If you've been experimenting with Anthropic's Claude Code, Claude Desktop, Cursor, or Cline, you've likely seen the Model Context Protocol (MCP) everywhere recently.
MCP is quickly becoming the USB-C standard for LLMs—a standardized open protocol that allows coding assistants and autonomous agents to safely call local tools, inspect databases, and interact with external APIs.
While the official documentation is comprehensive, starting from scratch can feel daunting with all the boilerplate.
Here is a working, minimal MCP server in ~30 lines of clean TypeScript using the official @modelcontextprotocol/sdk and zod.
Step 1: Initialize and Install Dependencies
In an empty folder, initialize your project and install the MCP SDK:
mkdir minimal-mcp && cd minimal-mcp
bun init -y # or npm init -y
bun add @modelcontextprotocol/sdk zod
Step 2: The Server Code (server.ts)
Create a file named server.ts:
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";
// 1. Initialize the MCP Server instance
const server = new Server(
{ name: "minimal-dev-tools", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// 2. Define your available tools schema
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "calculate_project_metrics",
description: "Calculates velocity and bug ratio from sprint numbers",
inputSchema: {
type: "object",
properties: {
completedPoints: { type: "number", description: "Completed story points" },
bugCount: { type: "number", description: "Number of discovered bugs" },
},
required: ["completedPoints", "bugCount"],
},
},
],
};
});
// 3. Handle Tool Invocations
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "calculate_project_metrics") {
const { completedPoints, bugCount } = request.params.arguments as {
completedPoints: number;
bugCount: number;
};
const defectDensity = (bugCount / Math.max(1, completedPoints)).toFixed(2);
return {
content: [
{
type: "text",
text: `Sprint Analysis: ${completedPoints} pts delivered. Defect density: ${defectDensity} bugs/pt. Status: ${bugCount > 5 ? "⚠️ Needs Review" : "✅ Healthy"}`,
},
],
};
}
throw new Error(`Tool not found: ${request.params.name}`);
});
// 4. Connect via Standard Input/Output (stdio)
async function run() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Minimal MCP Server running on stdio transport!");
}
run().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});
⚠️ Crucial Logging Rule: Always log diagnostics using
console.error(), NEVERconsole.log(). In stdio transport,stdoutis strictly reserved for JSON-RPC message framing between your MCP server and the AI client. Printing arbitrary strings tostdoutcorrupts the JSON stream and crashes the client!
Step 3: Connect It to Claude Desktop or Claude Code
To test it with Claude Desktop, add the server to your claude_desktop_config.json:
-
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json -
Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"dev-metrics": {
"command": "bun",
"args": ["run", "/absolute/path/to/minimal-mcp/server.ts"]
}
}
}
Restart Claude Desktop, and you'll see a small hammer icon with calculate_project_metrics available. Ask Claude:
"We completed 42 story points this sprint with 3 bugs, what are our sprint metrics?"
Claude will automatically call your local TypeScript handler and interpret the response.
What's Next?
From here, you can extend your server to query PostgreSQL databases, fetch API metrics, or execute local git commands.
(Tip: If you're building agents that trigger external alerts or transactional actions, check out hosted setups like SadaSend which bundle per-key recipient allowlists and anti-loop circuit breakers to prevent runaway token costs.)
Are you building custom MCP servers for your workflow? What tools are you giving your coding assistant? Drop your thoughts below!
Top comments (0)