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.
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.
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
// 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:
npm install openmeteoThe 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.
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?
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.
// ❌ 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:
- Token cost: More tools = more input tokens per request = more expensive + slower
- Decision quality: More choices = more LLM confusion = worse routing
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
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.
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
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.