Skip to main content
Technology & EngineeringMcp Server347 lines

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.

Quick Summary18 lines
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 lines
Paste into your CLAUDE.md or agent config

MCP 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/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.

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 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.
  • Pin your @modelcontextprotocol/sdk version and test upgrades explicitly.

Install this skill directly: skilldb add mcp-server-skills

Get CLI access →

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 Server327L

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 Server353L

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 Server226L

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 Server431L

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 Server287L

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.

Mcp Server390L