Building Your First Model Context Protocol (MCP) Server with TypeScript and Zod Anthropic's Model Context Protocol (MCP) provides an open standard for connecting AI models to external tools and data sources. A developer demonstrates building a production-grade MCP server using TypeScript, the official @modelcontextprotocol/sdk, and Zod for runtime validation, treating the AI architecture through the lens of modern distributed systems. 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. 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 : 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. 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. 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: 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.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: initialize request to the server containing its protocol version and client capabilities. initialize Response: tools , resources , or prompts . notifications/initialized Notification: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: postgres://users/schema or file:///logs/error.log . Resources are passive; the LLM reads them to gather context 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, or formatting structural JSON incorrectly. When building an MCP server in TypeScript, you do not write manual if/else checks or ad-hoc regular expressions to validate incoming tool arguments. Instead, you rely on Zod , a TypeScript-first schema declaration and validation library. MCP bridges the gap between probabilistic text generation and deterministic backend execution by combining LLM tool declarations with strict, runtime schema enforcement via Zod. When you define an MCP tool using the TypeScript SDK, you pass a Zod schema alongside the tool's metadata: // Conceptual schema definition demonstrating how Zod enforces runtime safety const CreateUserSchema = z.object { username: z.string .describe "The unique handle for the user" , age: z.number .int .positive .describe "The user's age in years" , email: z.string .email .optional .describe "Optional contact email" } ; Under the hood, the MCP SDK serializes these Zod schemas into standard JSON Schema specifications during the tool discovery phase. When the LLM Host requests the list of available tools, it receives these explicit JSON Schemas. The host then injects these schemas into the LLM's system prompt or tool-calling grammar such as OpenAI's function calling or Anthropic's tool use blocks , constraining the model's output generation to match the expected structure. When the LLM decides to invoke the tool, it sends a JSON-RPC request containing the raw argument payload. The MCP server intercepts this payload and passes it directly through the Zod schema's .parse or .safeParse method: "age": "twenty-five" instead of the integer 25 , Zod catches the mismatch immediately. Expected number, received string at "age" . The MCP server catches this error, serializes it into a standardized JSON-RPC error response, and sends it back to the LLM. To truly master the mechanics of building an MCP server in TypeScript, it helps to map abstract protocol mechanics to familiar software engineering paradigms. Think of the LLM Host such as Claude Desktop or an enterprise agent runner as the Operating System Kernel . The kernel possesses high-level intelligence and scheduling capabilities managing the conversation, deciding the overarching goal , but it is intentionally stripped of direct drivers for every possible peripheral device in the universe. An MCP Server is analogous to a Device Driver e.g., a graphics driver, a printer driver, or a file system driver . Before an operating system can interact with a specialized RAID controller, the manufacturer must write a driver that adheres to the OS's strict kernel extension interfaces. Similarly, before an AI model can interact with a proprietary SQL database or a local Git repository, a developer must write an MCP server that adheres to the Model Context Protocol specification. If you have experience building modern web APIs, the architectural design of an MCP server will feel remarkably familiar. In a GraphQL server, you define a schema using the GraphQL SDL, mapping types, queries, and mutations. For every field in that schema, you write a resolver function that fetches the required data from a database, microservice, or cache. In an MCP server, the relationship between tools and their execution handlers mirrors GraphQL resolvers precisely: Let us examine a complete, self-contained MCP server designed for a SaaS context. This example implements a simulated customer support metrics and user lookup tool. It uses the official @modelcontextprotocol/sdk and zod for strict runtime input validation, communicating over standard input/output stdio streams with an MCP host. bash /usr/bin/env node / SaaS Customer Support MCP Server This self-contained TypeScript file sets up a Model Context Protocol MCP server using the official @modelcontextprotocol/sdk. It exposes a tool for querying user account metrics and fetching application logs, designed to integrate with an AI assistant or supervisor agent running inside an MCP-compatible host. / import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; // ============================================================================ // Mock Database / SaaS Data Layer // ============================================================================ interface SaaSUser { id: string; email: string; plan: "free" | "pro" | "enterprise"; mrr: number; // Monthly Recurring Revenue status: "active" | "suspended" | "churned"; lastActive: string; } const MOCK USERS: Record