MCP server in TypeScript
Minimum-viable Model Context Protocol server with one tool, exposed over stdio for Claude Code / Desktop.
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: truecontent blocks — the LLM will retry productively. - Stream long-running tool output via
progressTokenupdates instead of one giant payload.