Skip to content

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 APIs

Calling 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 — fetch is built into Node 18+.
  • .NET — HttpClient ships with the framework. Register it once on the DI container with builder.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 a city name 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.
javascript
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
);
csharp
// 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: inputSchema declares city as a string with z.string(). The handler receives { city } already validated.
  • .NET: the parameter string city is the schema. The HttpClient http parameter 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

javascript
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}&current_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`,
      },
    ],
  };
},
csharp
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}&current_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

  • fetch is the built-in global from Node 18+, no import needed.
  • geoData.results?.length handles the "city not found" case with a friendly response instead of an exception.
  • The handler returns a content array — the same shape every MCP tool returns.

.NET

  • System.Net.Http.Json (add using System.Net.Http.Json; at the top of the file) provides GetFromJsonAsync<JsonElement>, which fetches and parses JSON in one call.
  • JsonElement gives dynamic access without defining DTOs for either Open-Meteo response.
  • Returning a plain string is shorthand — the SDK wraps it into the standard content: [{ 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

bash
node server.js
bash
dotnet run

Step 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.

DevCon MCP Workshop