MCP TypeScript Server
Building MCP servers in TypeScript using the official @modelcontextprotocol/sdk package. Covers project setup, the McpServer high-level API, defining tools with Zod schemas, stdio and SSE transports, streaming responses, error handling, and deployment as npm packages or standalone binaries.
You are an AI assistant helping developers build MCP servers in TypeScript. The official `@modelcontextprotocol/sdk` package provides both a low-level protocol handler and a high-level `McpServer` class. Your role is to guide developers through project setup, implementation patterns, and deployment.
## Key Points
- **Using `eval()` for computation tools** — never evaluate arbitrary code. Use a safe parser or sandbox.
- **Blocking the main thread** — use `async`/`await` for all I/O. A blocked server cannot respond to pings or cancellations.
- **Logging to stdout** — stdout is the transport channel for stdio servers. Use `console.error` or the MCP logging notification for debug output.
- **Hardcoding secrets** — read API keys from environment variables, never from source code.
- **Ignoring the shebang** — `#!/usr/bin/env node` is required for `npx` distribution to work correctly.
- **Not building before publishing** — ensure `dist/` contains compiled JavaScript. TypeScript source will not run directly via `npx`.
- Use Zod schemas for tool inputs — they provide runtime validation and auto-generate JSON Schema.
- Read configuration from environment variables passed via the client config.
- Keep dependencies minimal — every dependency increases install time for `npx` users.
- Test with the MCP Inspector before publishing.
- Add a `--version` flag and helpful error messages for misconfiguration.
- Use `process.on("SIGINT", ...)` to handle graceful shutdown and cleanup.skilldb get mcp-server-skills/MCP TypeScript ServerFull skill: 347 linesMCP TypeScript Server
You are an AI assistant helping developers build MCP servers in TypeScript. The official @modelcontextprotocol/sdk package provides both a low-level protocol handler and a high-level McpServer class. Your role is to guide developers through project setup, implementation patterns, and deployment.
Philosophy
TypeScript MCP servers benefit from strong typing, excellent JSON Schema support via Zod, and the npm ecosystem for distribution. The SDK provides two API levels: use McpServer (high-level) for most servers, and drop to Server (low-level) when you need fine-grained control over the protocol. Start with the high-level API and only go lower if you hit its limits.
Techniques
Project Setup
# Create a new project
mkdir my-mcp-server && cd my-mcp-server
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
# Initialize TypeScript
npx tsc --init --target es2022 --module nodenext --moduleResolution nodenext --outDir dist --rootDir src
Configure package.json:
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"bin": {
"my-mcp-server": "dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Minimal Server with stdio Transport
#!/usr/bin/env node
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
// Define a tool
server.tool(
"greet",
"Generate a greeting for the given name",
{ name: z.string().describe("Name to greet") },
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}! Welcome to MCP.` }],
})
);
// Connect via stdio
const transport = new StdioServerTransport();
await server.connect(transport);
Add the shebang line and make it executable. The stdio transport reads JSON-RPC messages from stdin and writes responses to stdout.
SSE Transport (Remote Server)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
const server = new McpServer({ name: "remote-server", version: "1.0.0" });
// Define tools, resources, prompts...
server.tool("ping", "Health check", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
// SSE endpoint for server-to-client messages
let transport: SSEServerTransport;
app.get("/sse", async (req, res) => {
transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
// POST endpoint for client-to-server messages
app.post("/messages", async (req, res) => {
await transport.handlePostMessage(req, res);
});
app.listen(3000, () => {
console.log("MCP server running on http://localhost:3000");
});
Streamable HTTP Transport (Recommended for Remote)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "streamable-server", version: "1.0.0" });
// Define your tools...
server.tool("echo", "Echo back the input", { message: z.string() }, async ({ message }) => ({
content: [{ type: "text", text: message }],
}));
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
app.post("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
app.get("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
app.delete("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
await server.connect(transport);
app.listen(3000);
Tools with Complex Schemas
server.tool(
"search_issues",
"Search GitHub issues with filters",
{
repo: z.string().describe("Repository in owner/name format"),
query: z.string().optional().describe("Search query text"),
state: z.enum(["open", "closed", "all"]).default("open")
.describe("Issue state filter"),
labels: z.array(z.string()).optional()
.describe("Filter by label names"),
limit: z.number().int().min(1).max(100).default(20)
.describe("Maximum results to return"),
},
async ({ repo, query, state, labels, limit }) => {
const [owner, name] = repo.split("/");
const issues = await github.searchIssues({ owner, name, query, state, labels, limit });
return {
content: [{
type: "text",
text: JSON.stringify(issues.map(i => ({
number: i.number,
title: i.title,
state: i.state,
labels: i.labels.map(l => l.name),
created: i.created_at,
})), null, 2),
}],
};
}
);
Resources with Templates
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
server.resource(
"issue-detail",
new ResourceTemplate("github://issues/{owner}/{repo}/{number}", {
list: async () => {
// Return recently accessed issues for discovery
const recent = await getRecentIssues();
return recent.map(i => ({
uri: `github://issues/${i.owner}/${i.repo}/${i.number}`,
name: `#${i.number}: ${i.title}`,
}));
},
}),
"A specific GitHub issue with full details",
async (uri, { owner, repo, number }) => {
const issue = await github.getIssue(owner, repo, parseInt(number));
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: `# ${issue.title}\n\n${issue.body}\n\n---\nState: ${issue.state}\nAuthor: ${issue.user.login}\nCreated: ${issue.created_at}`,
}],
};
}
);
Low-Level Server API
For fine-grained control, use the Server class directly:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "low-level-server", version: "1.0.0" },
{ capabilities: { tools: { listChanged: true } } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "compute",
description: "Evaluate a mathematical expression",
inputSchema: {
type: "object" as const,
properties: {
expression: { type: "string", description: "Math expression to evaluate" },
},
required: ["expression"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "compute") {
const expr = request.params.arguments?.expression as string;
// Safe math evaluation (never use eval!)
const result = safeMathEval(expr);
return {
content: [{ type: "text", text: String(result) }],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
Error Handling Patterns
server.tool(
"fetch_url",
"Fetch content from a URL",
{ url: z.string().url() },
async ({ url }) => {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
if (!response.ok) {
return {
isError: true,
content: [{ type: "text", text: `HTTP ${response.status}: ${response.statusText}` }],
};
}
const text = await response.text();
return { content: [{ type: "text", text }] };
} catch (err) {
if (err.name === "TimeoutError") {
return {
isError: true,
content: [{ type: "text", text: "Request timed out after 10 seconds" }],
};
}
return {
isError: true,
content: [{ type: "text", text: `Fetch failed: ${err.message}` }],
};
}
}
);
Distribution via npx
Package your server so users can run it without installing:
{
"name": "@myorg/mcp-server-github",
"version": "1.0.0",
"bin": {
"mcp-server-github": "dist/index.js"
},
"files": ["dist"]
}
Users configure it in their MCP client:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@myorg/mcp-server-github"],
"env": {
"GITHUB_TOKEN": "ghp_..."
}
}
}
}
Anti-Patterns
- Using
eval()for computation tools — never evaluate arbitrary code. Use a safe parser or sandbox. - Blocking the main thread — use
async/awaitfor all I/O. A blocked server cannot respond to pings or cancellations. - Logging to stdout — stdout is the transport channel for stdio servers. Use
console.erroror the MCP logging notification for debug output. - Hardcoding secrets — read API keys from environment variables, never from source code.
- Ignoring the shebang —
#!/usr/bin/env nodeis required fornpxdistribution to work correctly. - Not building before publishing — ensure
dist/contains compiled JavaScript. TypeScript source will not run directly vianpx.
Best Practices
- Use Zod schemas for tool inputs — they provide runtime validation and auto-generate JSON Schema.
- Read configuration from environment variables passed via the client config.
- Keep dependencies minimal — every dependency increases install time for
npxusers. - Test with the MCP Inspector before publishing.
- Add a
--versionflag and helpful error messages for misconfiguration. - Use
process.on("SIGINT", ...)to handle graceful shutdown and cleanup. - Pin your
@modelcontextprotocol/sdkversion and test upgrades explicitly.
Install this skill directly: skilldb add mcp-server-skills
Related Skills
MCP Auth and Security
Securing MCP servers with authentication, authorization, and defensive practices. Covers OAuth 2.1 integration for remote servers, API key management through environment variables, input validation and sanitization, rate limiting, sandboxing tool execution, path traversal prevention, and the principle of least privilege for tool design.
MCP Deployment
Deploying MCP servers across different environments and transports. Covers local deployment via stdio, remote deployment with SSE and streamable HTTP, Docker containerization, cloud deployment on AWS/GCP/Vercel, npx and uvx distribution for zero-install usage, configuration management, and production hardening.
MCP Fundamentals
Core architecture of the Model Context Protocol (MCP) — the open protocol from Anthropic that connects AI assistants to external tools and data sources. Covers JSON-RPC transport, capabilities negotiation, server lifecycle, the client-server interaction model, and how tools, resources, and prompts fit together.
MCP Patterns
Common architectural patterns for MCP servers — database servers, API wrappers, file system servers, multi-tool orchestration, caching strategies, error recovery, and composition patterns. Practical blueprints for building production-quality MCP servers that handle real-world complexity.
MCP Prompts
Defining prompt templates in MCP servers that AI clients can discover and invoke. Covers prompt definitions with arguments, dynamic prompt generation, multi-turn prompt structures, embedding resources in prompts, prompt discovery, and patterns for building reusable prompt libraries.
MCP Python Server
Building MCP servers in Python using the official mcp SDK and the FastMCP high-level pattern. Covers project setup with uv, defining tools with type hints, async handlers, resources, prompts, stdio and SSE transports, context objects, and deployment strategies including uvx distribution.