MCP Tools That Call External APIs

Build an MCP weather tool using the Open-Meteo API. Learn how LLMs select tools, why you should limit tool count to under 40, and how tool descriptions prevent bad routing.

April 1, 20268 min read4 / 7

The add tool proved the concept. Now let's build something that actually adds value: a tool that gives the LLM access to real-time weather data it couldn't otherwise know.

This is the moment MCP's value becomes tangible.


The Real Problem This Solves

Ask any LLM "What's the weather in Minneapolis right now?" and it'll say:

"I don't have access to real-time weather data. As of my knowledge cutoff..."

That's not a limitation of the LLM's intelligence -- it literally has no connection to live data. An MCP weather tool fixes this in 30 lines of code.

Diagram

Open-Meteo API

Open-Meteo is a free weather API -- no API key, no signup required. It accepts latitude/longitude and returns current conditions.

Brian specifically chose this one for the course after contacting them first:

"I told them we're about to hammer your API with a bunch of students. They said: we don't care, that's fine. They're hoping people start using it professionally."

If Open-Meteo ever goes down, there are hundreds of free APIs at public-apis.io you can substitute.


Building the Weather Tool

JavaScript
// weather.js import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { fetchWeatherApi } from "openmeteo"; const server = new McpServer({ name: "weather-server", version: "1.0.0", }); server.registerTool("get_weather", { title: "Get Current Weather", description: "Get the current weather for a location. Provide latitude and longitude. Use this when the user asks about current weather, temperature, rain, or wind conditions.", inputSchema: { latitude: z.number().describe("Latitude of the location"), longitude: z.number().describe("Longitude of the location"), } }, async ({ latitude, longitude }) => { const params = { latitude, longitude, current: ["temperature_2m", "apparent_temperature", "precipitation", "rain", "wind_speed_10m"], temperature_unit: "fahrenheit", wind_speed_unit: "mph", precipitation_unit: "inch", }; const responses = await fetchWeatherApi( "https://api.open-meteo.com/v1/forecast", params ); const current = responses[0].current(); const temp = current.variables(0).value(); const feelsLike = current.variables(1).value(); const precip = current.variables(2).value(); const rain = current.variables(3).value(); const wind = current.variables(4).value(); const summary = [ `Temperature: ${temp.toFixed(1)}°F (feels like ${feelsLike.toFixed(1)}°F)`, `Precipitation: ${precip.toFixed(2)} inches`, `Rain: ${rain.toFixed(2)} inches`, `Wind Speed: ${wind.toFixed(1)} mph`, ].join("\n"); return { content: [{ type: "text", text: summary }], }; }); const transport = new StdioServerTransport(); await server.connect(transport);

Install the package first:

Bash
npm install openmeteo

The LLM Infers Coordinates

The API requires coordinates -- but users say "Minneapolis" or "London". The LLM bridges this gap automatically.

When a user asks "What's the weather in Minneapolis?", the LLM knows Minneapolis is approximately 44.98°N, 93.27°W from its training data. It fills in the latitude and longitude parameters without asking the user to provide them.

JavaScript · Live Editor
Loading editor...
ℹ️ LLMs can be slightly imprecise with coordinates -- off by a few miles. For weather this is fine. For a mapping application where precision matters, you'd add a geocoding step in your tool itself.

How LLMs Select Tools

The LLM reads every tool description in context and makes a routing decision: which tool best matches what the user asked?

Diagram

The description drives the routing. If your description is vague, the LLM might pick the wrong tool. If it's too verbose, it might start hallucinating edge cases described in the text.

JavaScript
// ❌ Too vague -- LLM won't know when to use it description: "Gets weather" // ❌ Too verbose -- introduces uncertainty description: "Get the current weather for a specific geographic location on Earth. Only supports locations on land, not in the ocean. Do not use for historical weather data or forecasts beyond 7 days. Returns temperature in Fahrenheit unless the user specifies Celsius." // ✅ Clear and targeted description: "Get the current weather for a location. Provide latitude and longitude. Use this when the user asks about current weather, temperature, rain, or wind conditions."

The 40-Tool Limit

Every tool in your MCP server gets sent to the LLM as part of the system context -- its name, description, and full parameter schema. This uses tokens.

Claude degrades noticeably above ~40 tools. Two problems compound:

  1. Token cost: More tools = more input tokens per request = more expensive + slower
  2. Decision quality: More choices = more LLM confusion = worse routing
Diagram

What to Do About It

  • Only expose tools relevant to the current task
  • Claude Desktop lets you toggle tools on/off per session -- use this
  • Split large servers into multiple focused servers by domain
  • Kill tools you wrote but never actually use
⚠️ Don't give the LLM two tools that do similar things. If it has get_weather and fetch_current_conditions, it gets confused. It might call both, alternate between them unpredictably, or give up. One tool, one purpose.

LLM Temperature and Tool Selection

When you interact with an LLM, there's a hidden temperature parameter controlling how deterministic its outputs are.

Diagram

At higher temperatures, the LLM might occasionally decide not to use a tool it should use, or pick a slightly different approach. If you're seeing inconsistent tool use, temperature is one factor.

Most MCP clients use a temperature around 0.7–0.8, which gives a good balance of consistency and natural-feeling responses.


Lab -- Test Tool Selection Logic

JavaScript · Live Editor
Loading editor...
✅ Key insight: The LLM does this routing far better than naive keyword matching -- it understands semantic meaning, context, and intent. Your job is to write descriptions clear enough that the routing is obvious even with simpler matching.

Key Takeaways

  • LLMs have no live data access -- MCP tools give them real-time information
  • LLMs fill in parameters automatically -- city names become coordinates, dates become timestamps
  • Tool descriptions drive routing -- too vague or too verbose both hurt
  • Cap tool count around 40 -- beyond that, routing quality and token efficiency both suffer
  • Don't expose redundant tools -- two similar tools confuse the LLM more than having one

What's Next

Tools are the LLM's actions. Resources are different -- they're static context you push to the LLM. Next, we build a resource that exposes a database schema.