picoflow.io

Lesson 4: WeatherStep + Local MCP


Goal

Capture two city names, validate them, call a local MCP weather tool, persist the temperature into state, and branch once both cities are known.


Prompt and Tool Surface

public getPrompt(): string {
  return `
    ${DemoPrompt.TravelRole}
    Ask user to enter 2 city names to compare their current day temperature.
    Capture the names of the two cities and call tool 'get_weather' for each city.
    If user prefer to exit, call tool 'end_chat'.
  `;
}

public defineTool(): ToolType[] {
  return [{
    name: 'get_weather',
    description: 'capture the weather of one city',
    schema: z.object({ cityName: z.string() }),
  }];
}
public getTool(): string[] {
  return ['get_weather', 'end_chat'];
}
Tip

Restrict tools per step to minimize hallucinated actions and keep the LLM on the rails.


Local MCP Integration

The demo does not hard-code a weather result. Instead, WeatherStep calls callCityTemperatureMcpTool(...), which reaches a local MCP-backed weather service. That makes the step feel like a real tool-calling agent rather than a toy prompt-only flow.

const [weather] = await callCityTemperatureMcpTool([cityName]);
if (weather?.temperature !== null && weather?.temperature !== undefined) {
  const stateCityName = this.normalizeCityName(cityName);
  this.saveState({ [`city_${stateCityName}`]: weather.temperature });

  const LA = this.getState('city_LA');
  const NYC = this.getState('city_NYC');
  return LA && NYC ? FooLogicStep : WeatherStep;
}

Handler Logic

protected async get_weather(tool: ToolCall): Promise<ToolResponseType> {
  const cityName = tool.args?.cityName;
  if (typeof cityName !== 'string') {
    return { step: WeatherStep, tool: 'Only LA and NYC cities are allowed' };
  }

  const [weather] = await callCityTemperatureMcpTool([cityName]);
  if (weather?.temperature !== null && weather?.temperature !== undefined) {
    const stateCityName = this.normalizeCityName(cityName);
    this.saveState({ [`city_${stateCityName}`]: weather.temperature });
    const LA = this.getState('city_LA');
    const NYC = this.getState('city_NYC');
    return LA && NYC ? FooLogicStep : WeatherStep;
  }

  return { step: WeatherStep, tool: 'Only LA and NYC cities are allowed' };
}
  • Valid city → store the temperature in step state.
  • Both temperatures present → transition to FooLogicStep.
  • Invalid city → retry in place, surfacing the error as a tool result.

Patterns Illustrated

  • Local MCP tool use: the step reaches out to a local service instead of relying on an embedded stub.
  • Multi-call capture: stay in the same step until both city temperatures are collected.
  • Retry-in-place: { step: SameStep, tool: 'error' } keeps the LLM focused.
  • Stateful guardrails: step state drives deterministic branching.

Next we’ll capture the user’s name, inject runtime context, and run an InContext sub-agent.