Skip to main content
P7 Intermediate

Build an MCP Server

Create tools with createSdkMcpServer() and connect to agents.

SDK APIs createSdkMcpServer() tool() mcpServers mcp__*__*

Exercise 1: In-Process MCP Server

Create a weather MCP server using createSdkMcpServer() and tool(), then wire it into an agent.

Starter Code exercise-1.ts
import { query, createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Step 1: Create an in-process MCP server with tools
const weatherServer = createSdkMcpServer({
  name: "weather",
  tools: [
    tool("get_weather", "Get current weather for a city", {
      city: z.string().describe("City name"),
      units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
    }, async ({ city, units }) => {
      // Simulated weather data
      const data: Record<string, { temp: number; condition: string }> = {
        "tokyo":     { temp: 22, condition: "Partly cloudy" },
        "london":    { temp: 14, condition: "Rainy" },
        "new york":  { temp: 28, condition: "Sunny" },
        "sydney":    { temp: 19, condition: "Overcast" },
      };
      const weather = data[city.toLowerCase()] ?? { temp: 20, condition: "Unknown" };
      const temp = units === "fahrenheit" ? weather.temp * 9/5 + 32 : weather.temp;
      return { content: [{ type: "text", text: `${city}: ${temp}deg${units === "fahrenheit" ? "F" : "C"}, ${weather.condition}` }] };
    }),

    tool("get_forecast", "Get 3-day weather forecast for a city", {
      city: z.string().describe("City name"),
    }, async ({ city }) => {
      return { content: [{ type: "text", text: `${city} forecast: Today: 22C Sunny, Tomorrow: 19C Cloudy, Day 3: 17C Rain` }] };
    })
  ]
});

// Step 2: Connect the server to an agent
const response = query({
  prompt: "What's the weather like in Tokyo and London? Also get me Tokyo's 3-day forecast.",
  options: {
    mcpServers: { "weather": weatherServer }
  }
});

// Step 3: Stream messages and track tool calls
const toolCalls: string[] = [];

for await (const message of response) {
  if (message.type === "tool_call") {
    toolCalls.push(`${message.tool_name}(${JSON.stringify(message.tool_input)})`);
  }
  if (message.type === "result") {
    console.log(message.content);
  }
}

// Tools are namespaced as mcp__weather__get_weather and mcp__weather__get_forecast
console.log("\n[Tools Used]");
for (const call of toolCalls) {
  console.log(`  > ${call}`);
}

Your task: Run this code and observe the tool calls. Then add a third tool get_alerts that returns weather alerts for a region. Verify the agent uses all three tools appropriately.

Exercise 2: External MCP Server

Connect to a stdio-based external MCP server and verify its status with mcpServerStatus().

Exercise Code exercise-2.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

// Connect to an external stdio-based MCP server
// Example: GitHub MCP server via npx
const response = query({
  prompt: "List the 5 most recent issues in the anthropics/claude-code repo",
  options: {
    mcpServers: {
      "github": {
        type: "stdio",  // Required: "stdio" | "http" | "sse"
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-github"],
        env: {
          GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN!,
        },
      },
    },
  },
});

// Stream and collect the result
for await (const message of response) {
  if (message.type === "result") {
    console.log(message.content);
  }
}

// Check server health and available tools
const status = await response.mcpServerStatus();
for (const [name, info] of Object.entries(status)) {
  console.log(`\n[Server] ${name}`);
  console.log(`  Status: ${info.connected ? "Connected" : "Disconnected"}`);
  console.log(`  Tools: ${info.tools.map(t => t.name).join(", ")}`);
  console.log(`  Latency: ${info.latency_ms}ms`);
}

// Alternative: filesystem MCP server
const fsResponse = query({
  prompt: "List all markdown files in the docs/ directory",
  options: {
    mcpServers: {
      "filesystem": {
        type: "stdio",
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "./docs"],
      },
    },
  },
});

for await (const message of fsResponse) {
  if (message.type === "result") {
    console.log(message.content);
  }
}

Your task: Set up your GITHUB_TOKEN and run this code. Inspect the mcpServerStatus() output. Then try connecting to a different MCP server (e.g., the filesystem server) and verify its tool list.

Exercise 3: Multi-Server Agent

Connect 3 MCP servers simultaneously and build an agent that orchestrates tools across all of them.

Challenge exercise-3.ts
import { query, createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Server 1: Database (in-process)
const dbServer = createSdkMcpServer({
  name: "database",
  tools: [
    tool("query_users", "Query user records from the database", {
      filter: z.string().optional().describe("Filter string to match against user data")
    }, async ({ filter }) => {
      const users = [
        { id: 1, name: "Alice", email: "alice@example.com", city: "Tokyo" },
        { id: 2, name: "Bob", email: "bob@example.com", city: "London" },
        { id: 3, name: "Carol", email: "carol@example.com", city: "New York" },
      ];
      const filtered = filter
        ? users.filter(u => JSON.stringify(u).toLowerCase().includes(filter.toLowerCase()))
        : users;
      return { content: [{ type: "text", text: JSON.stringify(filtered) }] };
    })
  ]
});

// Server 2: Weather (in-process)
const weatherServer = createSdkMcpServer({
  name: "weather",
  tools: [
    tool("get_weather", "Get weather for a city", {
      city: z.string().describe("City name")
    }, async ({ city }) => {
      return { content: [{ type: "text", text: `${city}: 22C, Sunny` }] };
    })
  ]
});

// Server 3: Notifications (in-process)
const notifyServer = createSdkMcpServer({
  name: "notifications",
  tools: [
    tool("send_email", "Send an email notification", {
      to: z.string().email().describe("Recipient email address"),
      subject: z.string().describe("Email subject"),
      body: z.string().describe("Email body content")
    }, async ({ to, subject }) => {
      return { content: [{ type: "text", text: `Email sent to ${to}: "${subject}"` }] };
    })
  ]
});

// Connect all 3 servers to one agent
const response = query({
  prompt: `For each user in the database:
    1. Look up the weather in their city
    2. Send them a personalized email with their local weather forecast
    Report what you did.`,
  options: {
    mcpServers: {
      "database": dbServer,
      "weather": weatherServer,
      "notifications": notifyServer
    }
  }
});

// Stream and analyze tool usage
const serverUsage = new Map<string, number>();

for await (const message of response) {
  if (message.type === "tool_call") {
    // Tool names are prefixed: mcp__database__query_users, mcp__weather__get_weather, etc.
    const server = message.tool_name.split("__")[1];
    serverUsage.set(server, (serverUsage.get(server) ?? 0) + 1);
  }
  if (message.type === "result") {
    console.log(message.content);
  }
}

console.log("\n[Server Usage]");
for (const [server, count] of serverUsage) {
  console.log(`  ${server}: ${count} calls`);
}
← Back to Exercises