How to Build an MCP Server in TypeScript (Step-by-Step)

Build your first MCP server in TypeScript with the official SDK — tools, stdio transport, and a Claude Code test loop in under an hour.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

June 4, 2026
5 min read
How to Build an MCP Server in TypeScript (Step-by-Step)

Why Build an MCP Server in TypeScript?

The Model Context Protocol (MCP) standardizes how AI assistants interact with external tools and data. By building your own MCP server in TypeScript, you unlock custom integrations — internal APIs, deployment hooks, CMS bridges — without waiting for official servers. TypeScript gives you type safety, modern async patterns, and excellent SDK support.

This tutorial walks you through a minimal but production-ready MCP server using @modelcontextprotocol/sdk. You'll expose one tool over stdio, add a resource, and connect it to Claude Code for testing. Most developers can ship a useful internal server in under an hour.

Prerequisites

  • Node.js 20+ (LTS recommended)
  • pnpm (or npm/yarn)
  • A TypeScript project initialized
  • Claude Code for end-to-end testing (optional but recommended)
bash
mkdir my-mcp-server && cd my-mcp-server
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node

Configure tsconfig.json for ESM:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Add a build script to package.json:

json
"scripts": {
  "build": "tsc",
  "start": "node dist/index.js"
}

Step 1: Create a Minimal Server

Create src/index.ts with the following code:

typescript
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: 'pristren-internal', version: '1.0.0' });

server.tool(
  'get_deploy_status',
  'Returns last deploy status for an app slug',
  { app: z.string().describe('App slug e.g. zlyqor_web') },
  async ({ app }) => {
    // Replace with real API call
    const status = { app, env: 'production', ok: true, version: '1.2.3' };
    return {
      content: [{ type: 'text', text: JSON.stringify(status, null, 2) }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Explanation

  • McpServer: Core class from the SDK. You provide a name and version.
  • server.tool(): Registers a tool. First argument is the tool name (snake_case), second is a description, third is a Zod schema for parameters, fourth is the handler function.
  • StdioServerTransport: Communicates via standard input/output. Perfect for local development and Claude Code integration.
  • Handler return: Must include a content array with at least one text content item. You can also return images or embedded resources.

Build and test locally:

bash
pnpm build
node dist/index.js

The server will start and listen for JSON-RPC messages on stdin. You can test it manually with a tool like mcp-cli or connect it to Claude Code.

Step 2: Connect to Claude Code

Claude Code supports MCP servers via the claude mcp add command. Register your server:

bash
claude mcp add pristren-internal -- node dist/index.js

Now you can prompt Claude Code to use your tool:

"Use pristren-internal to check deploy status for zlyqor_web."

Claude will call the get_deploy_status tool with app: "zlyqor_web" and display the result. You can also chain multiple tools in one conversation.

Troubleshooting

  • If Claude doesn't find the server, verify the path to dist/index.js is absolute or relative to your working directory.
  • Check that the server starts without errors by running the command directly.
  • Use claude mcp list to see all registered servers.

Step 3: Add Resources (Read-Only Data)

Resources expose static data that the agent can read without executing shell commands. This is useful for configuration schemas, documentation, or reference data.

typescript
server.resource('config-schema', 'config://schema.json', async () => ({
  contents: [{ uri: 'config://schema.json', text: '{ "type": "object" }' }],
}));

Resources use URIs for identification. The handler returns an array of content items. You can also return binary data with MIME types.

Step 4: Add Prompts (Optional)

Prompts are reusable templates that guide the AI on how to use your tools. They're like meta-instructions.

typescript
server.prompt('deploy-check', 'Check deploy status for an app', {
  app: z.string().describe('App slug'),
}, ({ app }) => ({
  messages: [{
    role: 'user',
    content: {
      type: 'text',
      text: `Use the get_deploy_status tool to check the deploy status for ${app}.`,
    },
  }],
}));

Prompts are especially useful when you have complex workflows that require multiple tool calls.

Production Checklist

Before deploying your MCP server to production, consider these best practices:

  • Validate inputs with Zod: Always use Zod schemas to validate tool parameters. This prevents malformed requests and provides clear error messages.
  • Never return secrets in tool output: If your tool calls an internal API, strip sensitive fields like API keys or tokens before returning.
  • Log tool calls server-side: For audit and debugging, log each tool invocation with timestamp, parameters, and result size.
  • Compact responses: AI models have token limits. Return only essential data. For large datasets, consider pagination or summarization. See our guide on MCP token bloat.
  • Version semver: Use semantic versioning for your server. Pin the version in your team's .mcp.json to avoid breaking changes.
  • Error handling: Wrap tool handlers in try-catch and return user-friendly error messages.
typescript
server.tool('risky_operation', '...', { ... }, async (args) => {
  try {
    // ...
  } catch (error) {
    return {
      content: [{ type: 'text', text: `Error: ${error.message}` }],
      isError: true,
    };
  }
});

Advanced: HTTP Transport

For remote MCP servers, use HTTP transport instead of stdio. This allows multiple clients to connect over the network.

bash
pnpm add express
typescript
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const server = new McpServer({ name: 'remote-server', version: '1.0.0' });
// ... register tools

const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
  // Handle MCP protocol over HTTP
  // You'll need to implement JSON-RPC handling
});
app.listen(3000);

For production HTTP servers, add authentication (OAuth2, API keys) and rate limiting.

Common Pitfalls

  • Blocking the event loop: Tool handlers should be async and non-blocking. Avoid synchronous file reads or heavy computations.
  • Ignoring transport errors: Stdio transport can fail if the parent process dies. Implement reconnection logic for long-running servers.
  • Overly complex schemas: Keep Zod schemas simple. Use .describe() to help the AI understand parameters.
  • Missing error responses: Always return isError: true for failures so the AI knows the call failed.

Next Steps

  • Add OAuth for HTTP deployment
  • Publish internal server via private npm registry
  • Bundle a skill documenting which tools to call — Skills vs MCP

Keep Reading


Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace — chat, projects, time tracking, AI meeting summaries, and invoicing.

Frequently Asked Questions

What is an MCP server and why build one in TypeScript?

An MCP (Model Context Protocol) server is a lightweight service that exposes tools, resources, and prompts to AI assistants like Claude. Building it in TypeScript gives you type safety, modern async patterns, and seamless integration with the official `@modelcontextprotocol/sdk`. It's ideal for custom internal integrations without waiting for third-party servers.

How does an MCP server work with Claude Code?

Claude Code connects to MCP servers via stdio transport. You register your server with `claude mcp add`, and Claude can then call your tools during conversations. The server listens for JSON-RPC messages on stdin and returns results on stdout. This allows Claude to execute custom actions like checking deploy status or querying internal APIs.

What are best practices for building an MCP server in TypeScript?

Best practices include: validating inputs with Zod schemas, never returning secrets in tool output, logging tool calls for audit, keeping responses compact to avoid token bloat, using semantic versioning, handling errors gracefully with `isError: true`, and choosing the right transport (stdio for local, HTTP for remote with authentication).

How much does it cost to build and run an MCP server?

Building an MCP server is free — the SDK is open-source and you only need Node.js. Running costs depend on your infrastructure: a local server costs nothing, while a cloud-hosted HTTP server may incur minimal compute costs (e.g., $5–$20/month on a small VPS). The main cost is development time, which is typically under an hour for a basic server.

Is building an MCP server in TypeScript worth it in 2026?

Absolutely. MCP is becoming the standard for AI-tool integration, and TypeScript remains the most popular language for server-side development. Building your own server gives you full control over data and workflows, avoids vendor lock-in, and can be done quickly. It's especially valuable for teams with internal APIs or custom deployment pipelines.

#mcp#typescript#tutorial#claude-code#sdk

One AI engineering post, weekly

LLM benchmarks, prompt techniques, and token-cost breakdowns, delivered to your inbox.

Comments

Have a question or something to add? Sign in to join the discussion.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

Visionary technologist, software engineer, and machine learning specialist. Founder and CEO of Pristren, directing engineering teams that ship production-grade AI/ML pipelines, mission-critical full-stack applications, and developer tooling. Creator of Zlyqor, the unified team workspace platform. Author of 540+ technical guides and benchmark research reports on large language models, agentic workflows, Model Context Protocol (MCP), and modern web stacks.

PristrenZlyqor