Linking with External Tools
Connecting Your MCP Server to the Outside World
Where We Left Off
In the previous chapter you built a self-running MCP server with two tools, add and greet, and connected VS Code Copilot to it as the MCP client. Both tools were self-contained: they computed everything locally.
In this chapter we go further: your server's tools will call external APIs and other MCP servers, turning your server into a hub that bridges Copilot to the outside world.
Before: VS Code Copilot → Your MCP Server → local logic
Now: VS Code Copilot → Your MCP Server → external APIsCalling an External API from a Tool
The simplest external link is calling a public HTTP API directly from inside a tool handler.
We will use Open-Meteo - a free, no-API-key-required weather API.
🔗 open-meteo.com - free, no sign-up, no API key, no rate limit for basic use.
No new packages needed in either stack:
- Node.js —
fetchis built into Node 18+. - .NET —
HttpClientships with the framework. Register it once on the DI container withbuilder.Services.AddHttpClient()and you can inject it into any tool method.
Section 1 - The tool definition and schema
We'll register one tool in this part:
get_weather: takes acityname and returns the current temperature and wind speed. It resolves the city to coordinates using Open-Meteo's free geocoding API, then fetches the current weather for those coordinates. Two API calls, no key required.
server.registerTool(
"get_weather",
{
description: "Returns the current temperature for a given city by name",
inputSchema: {
city: z.string().describe("Name of the city"),
},
},
// handler — see Section 2
);// 1. Register HttpClient on the DI container, alongside the existing services
builder.Services.AddHttpClient();
builder.Services
.AddMcpServer(o =>
{
o.ServerInfo = new() { Name = "devcon-workshop-server", Version = "1.0.0" };
})
.WithHttpTransport(options => options.Stateless = true)
.WithTools<MathTools>()
.WithTools<GreetingTools>()
.WithTools<WeatherTools>();
// 2. Add the new tools class alongside the existing ones
[McpServerToolType]
public class WeatherTools
{
[McpServerTool(Name = "get_weather"),
Description("Returns the current temperature for a given city by name")]
public static async Task<string> GetWeather(
HttpClient http,
[Description("Name of the city")] string city)
{
// handler — see Section 2
}
}The tool only needs a city name. The handler resolves coordinates internally using Open-Meteo's free geocoding API, so callers — including LLM agents — just say "Amsterdam" and the tool handles the rest.
- Node.js:
inputSchemadeclarescityas a string withz.string(). The handler receives{ city }already validated. - .NET: the parameter
string cityis the schema. TheHttpClient httpparameter is supplied by the SDK from the DI container — any non-[Description]parameter is resolved as a service.
Section 2 - The handler: calling the external API
async ({ city }) => {
// Step 1: resolve city name to coordinates using Open-Meteo's free geocoding API
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
const geoRes = await fetch(geoUrl);
const geoData = await geoRes.json();
if (!geoData.results?.length) {
return { content: [{ type: "text", text: `City not found: ${city}` }] };
}
const { latitude, longitude } = geoData.results[0];
// Step 2: fetch current weather for those coordinates
const url =
`https://api.open-meteo.com/v1/forecast` +
`?latitude=${latitude}&longitude=${longitude}¤t_weather=true`;
const response = await fetch(url);
const data = await response.json();
const temp = data.current_weather.temperature;
const wind = data.current_weather.windspeed;
return {
content: [
{
type: "text",
text: `Weather in ${city}: ${temp}°C, wind ${wind} km/h`,
},
],
};
},public static async Task<string> GetWeather(
HttpClient http,
[Description("Name of the city")] string city)
{
// Step 1: resolve city name to coordinates using Open-Meteo's free geocoding API
var geoData = await http.GetFromJsonAsync<JsonElement>(
$"https://geocoding-api.open-meteo.com/v1/search?name={Uri.EscapeDataString(city)}&count=1");
if (!geoData.TryGetProperty("results", out var results) || results.GetArrayLength() == 0)
return $"City not found: {city}";
var lat = results[0].GetProperty("latitude").GetDouble();
var lon = results[0].GetProperty("longitude").GetDouble();
// Step 2: fetch current weather for those coordinates
var data = await http.GetFromJsonAsync<JsonElement>(
$"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t_weather=true");
var current = data.GetProperty("current_weather");
var temp = current.GetProperty("temperature").GetDouble();
var wind = current.GetProperty("windspeed").GetDouble();
return $"Weather in {city}: {temp}°C, wind {wind} km/h";
}Node.js
fetchis the built-in global from Node 18+, no import needed.geoData.results?.lengthhandles the "city not found" case with a friendly response instead of an exception.- The handler returns a
contentarray — the same shape every MCP tool returns.
.NET
System.Net.Http.Json(addusing System.Net.Http.Json;at the top of the file) providesGetFromJsonAsync<JsonElement>, which fetches and parses JSON in one call.JsonElementgives dynamic access without defining DTOs for either Open-Meteo response.- Returning a plain
stringis shorthand — the SDK wraps it into the standardcontent: [{ type: "text", text: ... }]payload for you.
Two API calls, zero extra packages — both Open-Meteo endpoints are free with no key required. The client sees a normal tool response and has no idea what happened inside.
View complete server.js in Source Code → · View complete Server.cs in Source Code →
Try it in Copilot Chat
Step 1 - Start the server
node server.jsdotnet runStep 2 - Restart the MCP connection
Open the Command Palette and run MCP: List Servers → Restart.
Step 3 - Ask Copilot
Open Copilot Chat in Agent mode and ask:
What's the weather in Amsterdam?Copilot calls your get_weather tool and returns the result.