MCP server in TypeScript

Minimum-viable Model Context Protocol server with one tool, exposed over stdio for Claude Code / Desktop.

MCP server in TypeScript

The Model Context Protocol (MCP) is the way LLM clients (Claude Desktop, Claude Code, Cursor, Cline) discover and invoke tools. Below is the smallest useful server: one tool, stdio transport, JSON Schema validation.

Install

npm i @modelcontextprotocol/sdk zod

Server

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

const server = new Server(
  { name: "weather-mcp", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

const GetWeatherInput = z.object({
  city: z.string().describe("City name, e.g. Bengaluru"),
});

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "get_weather",
      description: "Return current weather for a city.",
      inputSchema: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name !== "get_weather") {
    throw new Error(`Unknown tool: ${req.params.name}`);
  }
  const { city } = GetWeatherInput.parse(req.params.arguments);
  // Replace with a real API call
  const tempC = 28;
  return {
    content: [{ type: "text", text: `${city}: ${tempC}°C, partly cloudy.` }],
  };
});

await server.connect(new StdioServerTransport());

Wire it into Claude Code

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["./dist/server.js"]
    }
  }
}

Tips from production

  • Keep tool descriptions tight — descriptions are LLM-facing and a major lever for selection accuracy.
  • Beyond ~10–15 tools per server, classification accuracy on small models (Haiku) drops. Split into multiple servers, or use a Proxy Aggregator.
  • Always validate inputs with Zod and return structured errors via isError: true content blocks — the LLM will retry productively.
  • Stream long-running tool output via progressToken updates instead of one giant payload.