All Guides & Posts
Tutorials
380 views

How to Build a Custom MCP Server in TypeScript: A Hands-On Engineering Guide

Step-by-step code tutorial: Learn how to construct a production-ready Model Context Protocol (MCP) server in TypeScript from scratch, complete with schema validation, tool definitions, and live debugging.

7 min read• 2026-05-15
How to Build a Custom MCP Server in TypeScript: A Hands-On Engineering Guide

The Challenge: Giving AI Safe, Structured Access to Internal Tools

Imagine you are leading an engineering sprint and your team wants to give your AI assistants—like Claude Code or Cursor—the ability to inspect live production telemetry, query customer database records, and execute automated deployment health checks. If you try to do this by pasting database credentials directly into system prompts, you introduce catastrophic security risks and exhaust prompt context tokens.

The modern, professional solution is to build a dedicated Model Context Protocol (MCP) server. By wrapping your internal business logic in a lightweight TypeScript server, you create a strictly typed, sandboxed bridge that exposes only the exact functions and parameters your AI agents need.

In this hands-on tutorial, we will write a production-grade TypeScript MCP server from scratch. We will define schema-validated tools using Zod, handle errors gracefully, and connect the server directly to Claude Code and Ruflo swarms in under 15 minutes.

Step 1: Initializing the Project and Installing the MCP SDK

Let's start by scaffolding a new Node.js / TypeScript project with modern ESM support. Open your terminal and create a dedicated directory: 'mkdir my-custom-mcp && cd my-custom-mcp'. Initialize the package configuration by running 'npm init -y'.

Next, install the official Model Context Protocol TypeScript SDK along with Zod for robust runtime schema validation: 'npm install @modelcontextprotocol/sdk zod'. For our TypeScript build pipeline, install the necessary developer dependencies: 'npm install -D typescript @types/node tsx'.

Create a `tsconfig.json` file in your root directory configured for ES2022 and NodeNext module resolution. In your `package.json`, set `"type": "module"` and add a start script: `"start": "tsx src/index.ts"`. Your development environment is now ready for server development.

Step 2: Constructing the MCP Server and Defining Tools

Step 2: Constructing the MCP Server and Defining Tools

Create a new file at `src/index.ts`. We will instantiate the `Server` class from the MCP SDK and configure the standard input/output transport layer (`StdioServerTransport`).

Let's define a real-world tool: `calculate_database_stats`. This tool will accept a database table name, a date range string, and a boolean flag to include index fragmentation analysis. We define the input schema using Zod, which the MCP SDK automatically converts into JSON Schema format for the LLM to inspect.

When the AI invokes the tool, our handler function parses the arguments, executes the database query securely using parameterized statements, formats the results into clean JSON or Markdown, and returns a structured response payload containing `content: [{ type: 'text', text: result }]`.

By encapsulating the database credentials inside the server process, the LLM never sees raw connection strings or API keys—it only receives clean, sanitized query results.

Step 3: Connecting Your Custom Server to Claude Code and Cursor

Now that your TypeScript server is written, let's connect it to Claude Code so you can invoke your custom tools during live coding sessions.

Open your terminal and register the server with Claude Code by running: 'claude mcp add my-custom-mcp -- tsx /absolute/path/to/my-custom-mcp/src/index.ts'. Claude Code will verify the stdio connection and output a confirmation listing all discovered tools.

Launch Claude Code and test the integration: 'claude> Query our internal database stats for the users table and summarize recent registration trends'. Claude will immediately invoke your custom TypeScript tool, receive the sanitized data payload, and deliver a comprehensive analysis directly in your terminal.

To connect the same server to Cursor, simply add the server command to your `.cursor/mcp.json` configuration file, demonstrating the immense power of write-once, run-anywhere MCP standards.

Step 4: Production Hardening: Logging, Error Boundaries, and Sandboxing

When deploying custom MCP servers in production enterprise environments, adhere to these critical engineering practices:

1. Never Log to stdout: Because the stdio transport uses standard output for JSON-RPC message serialization, any accidental `console.log()` will corrupt the protocol stream and crash the client connection. Always direct debug logs to standard error (`console.error()`) or write to a dedicated log file.

2. Enforce Strict Input Sanitization: Never pass LLM-generated strings directly into shell commands or raw SQL queries. Always validate inputs against strict Zod schemas and use parameterized database drivers to prevent prompt injection and injection vulnerabilities.

3. Implement Request Timeouts: Wrap long-running operations (like heavy API requests or large file parsing) in `AbortController` timeouts to prevent agent threads from hanging indefinitely when external services degrade.

Frequently asked questions

Why shouldn't I use console.log in a stdio MCP server?

In stdio mode, standard output is reserved exclusively for JSON-RPC protocol messages. Calling console.log outputs unformatted text that breaks the client's JSON parser. Use console.error instead.

Can my TypeScript MCP server connect to real PostgreSQL databases?

Yes! You can import standard database clients like `pg`, `Prisma`, or `Kysely` inside your server to execute parameterized queries on behalf of the agent.

How do I update tool schemas without restarting the host editor?

The MCP specification supports dynamic tool list notifications (`notifications/tools/list_changed`), allowing servers to broadcast schema updates to active clients in real time.

Can I distribute my custom MCP server as an npm package?

Yes! You can publish your server to npm with an executable bin entry, allowing users to run it instantly via `npx my-mcp-server`.

How do I handle authentication for remote SSE servers?

For SSE HTTP transports, you can pass Bearer authentication tokens in the request headers during connection handshake and validate them in your Express or Fastify middleware.

Is TypeScript better than Python for building MCP servers?

Both TypeScript and Python have first-class official SDKs. TypeScript is ideal for Node.js environments and web tooling, while Python is popular for data science and machine learning workflows.

Related Guides & Documentation