DEV Community

Building Your First Model Context Protocol (MCP) Server with TypeScript and Zod

If you’ve been building AI agents or working with Large Language Models recently, you’ve likely hit the integration wall. Historically, connecting an LLM like Claude or GPT-4 to external environments-such as querying a production database, checking system logs, or interacting with a local file system-required building ad-hoc, brittle integration layers.

Every single AI framework required bespoke tool definitions, custom JSON-parsing loops, and hardcoded prompt engineering just to handle basic errors. Building these custom integrations felt remarkably like the early days of web development before HTTP standardization, where every browser vendor implemented proprietary rendering engines, forcing developers to maintain fragmented codebases.

Enter the Model Context Protocol (MCP) . Developed by Anthropic, MCP establishes an open, universal standard for connecting AI models to data sources and tools. In this comprehensive guide, we will dive deep into the architectural foundations of MCP, explore how it maps cleanly to modern web development paradigms like microservices, and build a production-grade, self-contained MCP server from scratch using TypeScript, the official @modelcontextprotocol/sdk , and Zod for strict runtime input validation.

Why MCP? The Architectural Paradigm Shift

To truly grasp why building an MCP server in TypeScript is a game-changer for AI application development, we have to look at how LLMs historically interacted with external environments. In a traditional setup, when an AI model needs to fetch data, developers write custom code that wraps APIs in rigid prompt instructions. However, models are probabilistic. They generate text token by token, meaning they are prone to subtle hallucinations regarding data types, missing required arguments, or formatting structural JSON incorrectly. MCP solves this fragmentation by treating the AI architecture through the lens of modern distributed systems, mapping the Model Context Protocol Client-Server Architecture directly to the classic Microservices Architecture via REST and gRPC :

  • The LLM Host (The API Gateway): Applications like Claude Desktop, IDE extensions, or custom agent runtimes act as the host environment. The host manages the user interface, maintains the conversational context, and hosts the LLM itself. However, the host intentionally lacks direct access to your local machine’s internal databases, proprietary file systems, or specialized enterprise tools.
  • The MCP Server (The Microservice): An independent, self-contained process-often written in TypeScript-that encapsulates specific tools, resources, and prompt templates. It knows nothing about the broader conversation history or the user’s overarching intent; its sole job is to expose a strictly typed, discoverable capability registry.
  • The Transport Layer (The Network Protocol): Just as microservices communicate via HTTP/2 or gRPC, MCP communication relies on robust transport mechanisms such as standard input/output (stdio) streams for local processes or Server-Sent Events (SSE) for remote networked servers.

When an AI model requires data or needs to execute an action, the LLM Host does not execute custom Python or TypeScript scripts directly. Instead, it queries the connected MCP servers to discover available tools, inspects their schemas, and delegates execution. The MCP server processes the request, interacts with the local system, and returns a standardized response payload. This decoupling ensures that security boundaries are strictly maintained: the LLM never executes arbitrary code on your system; it merely requests that an explicit, sandboxed MCP tool executes a predefined function.

The Anatomy of MCP: Transports, JSON-RPC, and Lifecycle Management

At its core, MCP is not a complex machine learning framework. Rather, it is a clean, rigorously engineered JSON-RPC 2.0 protocol running over structured input/output streams. When you boot an MCP server, it does not immediately start listening for AI tokens. Instead, it enters a precise, multi-phase lifecycle governed by initialization handshakes, capability negotiations, and continuous state synchronization.

1. The Transport Layer: Stdio and SSE

Communication between the MCP Host and the MCP Server must be decoupled from the transport mechanism. The TypeScript SDK provides abstract transport interfaces, primarily supporting two modes:

  • Stdio Transport: This is the default and most common mode for local development. The MCP Host spawns your TypeScript server as a child process using child_process.spawn. Communication happens entirely through standard input (process.stdin) and standard output (process.stdout). This is exceptionally secure because no network ports are opened, eliminating entire classes of network-based vulnerabilities. Logs and debugging statements must be strictly routed to standard error (process.stderr), as any stray console.log on stdout will corrupt the JSON-RPC message stream.
  • SSE (Server-Sent Events) Transport: Used for remote, distributed architectures. The server runs as a standalone HTTP service, streaming events down to the client while accepting tool execution commands via HTTP POST requests.

2. The Initialization Handshake

Once the transport channel is established, neither side assumes the other’s capabilities. The handshake proceeds through a rigid sequence of JSON-RPC messages:

  • initialize Request: The host sends an initialize request to the server containing its protocol version and client capabilities.
  • initialize Response: The server responds with its own protocol version, server metadata (name and version), and a declarations map of its supported capabilities-explicitly stating whether it supports tools, resources, or prompts.
  • notifications/initialized Notification: Once the host receives the server’s capabilities, it sends a final notification confirming that the initialization phase is complete, and normal operational traffic can begin.

3. Capability Separation: Tools vs. Resources

A common point of confusion for developers new to MCP is distinguishing between Tools and Resources. While both expose data to the LLM, their semantic contracts are fundamentally different:

  • Resources (Read-Only Data): Resources represent static or dynamic contextual data that the LLM can read. Think of them as read-only files, database schemas, or API endpoints identified by URIs (e.g., postgres://users/schema or file:///logs/error.log). Resources are passive; the LLM reads them to gather context before formulating an answer or deciding which tool to invoke.
  • Tools (Active Execution): Tools represent capabilities that can modify state or perform actions. Think of them as functions that write to a database, send an email, execute a shell command, or query a live pricing API. Tools require explicit inputs (arguments) and return execution results. Crucially, tools are active; the LLM explicitly decides to call a tool based on the user’s prompt, passing structured parameters validated at runtime.

The Role of Strict Validation: Zod as the Contract Enforcer

In traditional web applications, input validation is important for security and data integrity. In AI agent architectures, strict input validation is an absolute necessity. Because Large Language Models generate text probabilistically, they are prone to subtle hallucinations regarding data types, missing required arguments

Comments

No comments yet. Start the discussion.