TypeScript AI Agent and MCP Server Development Guide
Guide to building AI agents with TypeScript using Model Context Protocol server patterns. MCP architecture, tool definition, and agent orchestration covered.
MCP Package Downloads (NPM)
Core Capabilities: Tools, Resources, Prompts
Auth Standard (2026 Roadmap)
Wire Protocol for All Transports
Key Takeaways
The Model Context Protocol has moved from an Anthropic research project to the default integration standard for AI agent tooling in under eighteen months. As of early 2026, MCP has surpassed 97 million NPM downloads, ships natively in Claude Desktop, ChatGPT, Gemini AI Studio, and every major code editor with AI capabilities. The protocol is no longer experimental — it is infrastructure.
For TypeScript developers, this represents both an opportunity and an obligation. An opportunity because MCP servers are TypeScript- native, with the official SDK providing full type safety across tool definitions, resource schemas, and prompt templates. An obligation because the AI agents your team builds — or the AI agents your clients use — need standardized access to your systems. Building that access layer as an MCP server rather than a bespoke integration means building it once and supporting every compliant AI client automatically.
This guide covers the complete development workflow: from initializing an MCP server project to defining tools, exposing resources, creating prompt templates, implementing authentication, and deploying to production. For context on where MCP fits within the broader AI agent protocol ecosystem including A2A, ACP, and UCP, that comparison covers how MCP relates to inter-agent communication protocols.
What Is the Model Context Protocol
The Model Context Protocol is an open standard that defines how AI applications connect to external data sources and tools. Think of it as USB-C for AI agents: a universal connector that replaces the fragmented landscape of custom integrations with a single, standardized interface. Before MCP, connecting an AI agent to a database required writing database-specific code. Connecting that same agent to a CRM required writing CRM-specific code. Each integration was a one-off engineering effort.
MCP eliminates that per-integration overhead. You build an MCP server that exposes your system's capabilities — what it can do, what data it holds, what workflows it supports — through a standardized protocol. Any MCP-compliant AI client can then discover and use those capabilities without custom integration code. The server describes itself; the client consumes that description dynamically.
Executable functions the LLM can invoke. Tools accept structured input, perform operations (API calls, database queries, file manipulation), and return structured results. They are the “actions” an agent can take.
Structured data that clients can read. Resources work like virtual file systems — each has a URI, a MIME type, and content. They expose data without executing operations, keeping read and write concerns separated.
Pre-defined interaction templates with structured arguments. Prompts guide AI interactions toward specific workflows, ensuring consistent and reliable agent behavior across different use cases.
The protocol communicates over JSON-RPC 2.0, which means every message between client and server is a structured JSON object with a method name, parameters, and either a result or an error. This is the same wire format used by the Language Server Protocol (LSP) that powers IDE features like autocomplete and diagnostics. If you have built or consumed an LSP server, the MCP architecture will feel familiar.
MCP Architecture and Transport Layers
MCP uses a client-server architecture where the host application (Claude Desktop, an IDE, a custom agent) runs an MCP client that connects to one or more MCP servers. Each server maintains a one-to-one connection with the client through a transport layer. The transport handles the raw communication; the protocol layer on top handles capability negotiation, request routing, and lifecycle management.
Communication through standard input and output streams. The client spawns the server as a child process and sends JSON-RPC messages via stdin, receiving responses on stdout. Ideal for local development, CLI tools, and desktop applications.
console.log() in STDIO-based MCP servers. STDIO transport uses stdout for JSON-RPC messages. Logging to stdout corrupts the message stream. Use console.error() instead — it writes to stderr, which is separate from the transport channel.HTTP-based transports that enable remote MCP servers. SSE (Server-Sent Events) provides server-to-client streaming over HTTP while the client sends requests via POST. Streamable HTTP is the newer transport option that supports full bidirectional communication with session management, making it suitable for production deployments where multiple clients connect simultaneously and sessions persist across requests.
The architecture also includes a capability negotiation phase during connection initialization. When a client connects, the server declares what it supports — which tools are available, which resources can be read, which prompt templates are offered. The client uses this declaration to understand what the server can do before making any requests. This is fundamentally different from a REST API where the client must already know the endpoint structure.
For production deployments, the 2026 MCP roadmap includes OAuth 2.1 with PKCE for browser-based agent authentication and integration with enterprise identity providers like Okta and Azure AD. This moves MCP from developer tooling into enterprise infrastructure, where authentication and access control are table stakes.
Setting Up a TypeScript MCP Server
The official TypeScript SDK is published as @modelcontextprotocol/sdk on NPM. A minimal MCP server requires four things: a project with TypeScript configured for ES modules, the SDK installed, an entry file that creates a server instance, and a build step that produces executable JavaScript.
Project Initialization Steps
- 1.Create a new directory and run
npm init -yto initialize the project. Set"type": "module"in package.json to enable ES module syntax. - 2.Install dependencies:
npm install @modelcontextprotocol/sdk zodandnpm install -D typescript @types/node. Zod is used for schema validation on tool inputs. - 3.Configure tsconfig.json with
target: "ES2022",module: "Node16", andmoduleResolution: "Node16". Output to adist/directory. - 4.Create
src/index.tsas the server entry point. ImportMcpServerfrom the SDK andStdioServerTransportfor local development. - 5.Add a build script to package.json:
"build": "tsc". Add a shebang line#!/usr/bin/env nodeat the top of the entry file for STDIO execution.
The server entry file follows a consistent pattern: create a server instance with a name and version, register capabilities (tools, resources, prompts), then connect the server to a transport. The McpServer class handles all protocol-level concerns — capability negotiation, request routing, error formatting — so your code focuses entirely on implementing the actual functionality.
The entry file creates a new McpServer with a configuration object specifying the server name, version string, and capability declarations. You then call server.tool(), server.resource(), and server.prompt() to register each capability. Finally, you call server.connect(transport) to start listening.
Defining Tools for LLM Consumption
Tools are the most commonly used MCP capability. A tool is a function that an AI agent can call to perform an action: querying a database, calling an external API, reading a file, sending an email, running a calculation. Each tool has a name, a description that helps the LLM understand when to use it, an input schema defined with Zod, and an async handler function.
The description is critically important. LLMs decide which tool to call based on the description, not the tool name. A vague description like “gets data” forces the LLM to guess. A precise description like “retrieves the current weather forecast for a US state, including temperature, wind speed, and precipitation probability for the next 24 hours” gives the LLM enough context to make correct tool selection decisions consistently.
- Descriptive name that indicates the action:
get_weather_forecast - Detailed description with input/output expectations
- Strict Zod schema with field descriptions
- Structured error responses with
isError: true - Single-responsibility: one tool, one action
- ×Vague descriptions that force LLM guessing
- ×No input validation — trusting LLM output blindly
- ×Throwing unhandled exceptions instead of returning errors
- ×God tools that combine multiple unrelated operations
- ×Missing Zod field descriptions that aid LLM parameter selection
The tool handler function receives the validated input object and must return a result object with a content array containing one or more content blocks. Each block has a type (text, image, or resource) and corresponding data. For most tools, a single text content block with a JSON-stringified result is sufficient. The LLM parses the text content and extracts the information it needs.
.describe() to every Zod field in your tool input schemas. The description text is sent to the LLM during capability negotiation and directly influences how accurately the LLM fills in parameters. Undescribed fields lead to incorrect parameter values and failed tool calls.Exposing Resources and Data
Resources provide read-only access to data through a URI-based addressing scheme. Each resource has a unique URI (like config://app/settings or db://users/active), a MIME type, and content that can be text or binary. Resources are fundamentally different from tools: they expose data that the LLM can read and reason about, but they do not execute operations or modify state.
The distinction matters for security and predictability. A client can read all available resources without side effects. Tools, by contrast, perform actions that may change state. Keeping these concerns separate means you can grant resource access more liberally than tool access — an AI agent can read configuration data and documentation without being authorized to modify anything.
Common Resource Patterns
Static Resources
- • Configuration files and environment settings
- • API documentation and schema definitions
- • Application logs and audit trails
- • Template libraries and style guides
Dynamic Resources (Templates)
- • Database query results:
db://users/{userId} - • API response caches:
api://weather/{city} - • Generated reports:
report://monthly/{month} - • User profiles:
profile://{email}
Resource templates use URI patterns with placeholders that the client fills in at request time. The server registers a template like db://users/{userId} and the handler receives the resolved URI with the actual user ID. This enables dynamic data access without requiring a separate tool for each data retrieval pattern. The MCP SDK supports subscription-based resources as well, where clients can subscribe to changes and receive notifications when resource content updates.
Prompt Templates and Workflows
Prompts are the third MCP capability and the least understood. They are pre-defined interaction templates that guide how an AI agent uses your server's tools and resources. A prompt template has a name, a description, optional arguments, and returns a structured message sequence that the client inserts into the conversation.
The practical use case is workflow standardization. Instead of relying on the LLM to figure out the correct sequence of tool calls for a complex task, you encode that sequence in a prompt template. A “generate-report” prompt might specify: first read the project configuration resource, then call the data-query tool with specific parameters, then call the format-output tool with the results. The LLM executes this predefined workflow rather than improvising.
Single-purpose templates for common operations. Examples: “summarize-issue” reads a ticket resource and returns a structured summary. “check-deployment” calls monitoring tools and formats the status.
Multi-step templates that orchestrate several tools and resources in sequence. Examples: “onboard-customer” creates a record, sends a welcome email, and schedules a follow-up task using three separate tools.
Templates that configure agent behavior for a specific domain. They embed system instructions, define available actions, and set guardrails. Useful for creating specialized agents from a general-purpose MCP server.
Prompt templates accept arguments defined with the same Zod-based schema system used by tools. The handler function receives validated arguments and returns a messages array containing role/content pairs. The client presents these messages to the LLM, which then follows the instructions to execute the workflow using the server's tools and resources.
Authentication and Rate Limiting
An MCP server without authentication is an unauthenticated API endpoint that autonomous agents will call at machine speed. This is the most consequential security consideration in MCP server development. STDIO-based servers running locally inherit the user's system permissions and are implicitly authenticated. Remote servers running over SSE or Streamable HTTP need explicit authentication.
- OAuth 2.1 with PKCE — the MCP standard for browser-based and remote agents. Ships in the 2026 specification update.
- API key headers — simpler alternative for server-to-server connections. Validate keys in transport middleware before requests reach handlers.
- mTLS certificates — mutual TLS for high-security environments where both client and server authenticate with certificates.
- Per-client limits — throttle requests per authenticated client using token bucket or sliding window algorithms.
- Per-tool limits — expensive operations (database writes, external API calls) get stricter limits than read-only operations.
- Cost-based limits — assign cost weights to tools and limit total cost per time window rather than raw request count.
Rate limiting for MCP servers requires different thinking than rate limiting for human-facing APIs. AI agents execute tool calls in rapid sequences — an agent might call five tools in under a second during a multi-step workflow. Setting rate limits too low breaks legitimate agent workflows. Setting them too high exposes your backend to abuse. The practical approach is to start with generous per-client limits and add per-tool limits for operations that touch expensive resources.
For TypeScript implementations, middleware-based rate limiting works at the transport level. Intercept incoming JSON-RPC requests before they reach the MCP server's handler layer. Redis-backed rate limiters (like Upstash) work well for distributed deployments where multiple server instances share rate limit state. For single-instance deployments, in-memory rate limiting with a sliding window counter is sufficient.
Connecting to LLM Backends
A key advantage of the MCP approach is backend agnosticism. The MCP server you build works with any compliant client, regardless of which LLM powers it. Claude, GPT, Gemini, Llama, and open- source models running locally can all consume the same MCP server. The server does not need to know or care which model is calling its tools.
Major MCP Client Implementations
AI Assistants
- • Claude Desktop and Claude API
- • ChatGPT and OpenAI Assistants API
- • Google Gemini (AI Studio, Vertex AI)
- • Amazon Q and Bedrock agents
Developer Tools
- • VS Code with GitHub Copilot
- • Cursor and Windsurf editors
- • JetBrains IDEs (IntelliJ, WebStorm)
- • Zed editor
When building an MCP server, you design your tools, resources, and prompts for the protocol layer, not for any specific model. The tool descriptions, input schemas, and output formats should be clear enough that any capable LLM can use them correctly. This means writing descriptions in natural language that is unambiguous, providing field-level descriptions on every Zod schema property, and returning results in structured formats that are easy for models to parse.
For teams that want to build their own MCP client — for example, embedding MCP tool consumption into a custom agent built with the multi-protocol agent patterns — the TypeScript SDK includes a Client class that handles capability discovery, tool invocation, resource reading, and prompt retrieval. Building both the server and client in TypeScript gives you end-to-end type safety through shared schema definitions.
Testing, Debugging, and Deployment
The MCP ecosystem includes a dedicated testing tool: the MCP Inspector. It is a developer tool that connects to your server as a client, displays all registered capabilities, and lets you invoke tools, read resources, and trigger prompts manually. This is invaluable during development because it removes the LLM from the testing loop — you can verify that your server works correctly before testing with an actual AI agent.
Test tool handler functions in isolation with mocked dependencies. Verify that valid inputs produce expected outputs and invalid inputs return structured errors. Use Vitest or Jest with TypeScript for full type coverage in test assertions.
Spin up the server with a test client and exercise the full request lifecycle: capability negotiation, tool invocation, resource reading, prompt retrieval. The SDK's InMemoryTransport enables fast integration tests without network overhead.
STDIO servers deploy as NPM packages or standalone binaries (via pkg or esbuild bundling). HTTP- based servers deploy to any Node.js hosting: Vercel Functions, AWS Lambda, Railway, Fly.io, or containerized on Kubernetes.
For debugging, MCP servers should log all incoming requests and outgoing responses at the transport level. In STDIO mode, logging goes to stderr. In HTTP mode, logging goes to your standard observability stack. Include the request method, tool name, input parameters (sanitized to exclude sensitive data), execution duration, and response status in every log entry. When an agent makes an unexpected tool call or receives an unexpected result, these logs are the primary debugging tool.
Production deployment should include health checks, graceful shutdown handling, and connection lifecycle management. The SDK provides lifecycle hooks for server startup and shutdown. Use them to initialize database connections, warm caches, and clean up resources. For Streamable HTTP deployments, implement session persistence so that long-running agent workflows survive server restarts without losing conversation state.
npx @modelcontextprotocol/inspector to launch the Inspector. Point it at your server's entry file for STDIO or at your server's URL for HTTP transport. The Inspector shows all registered tools, resources, and prompts with their schemas, and lets you test each capability interactively.Conclusion
The Model Context Protocol has become the standard interface between AI agents and external systems. Building MCP servers in TypeScript is not a speculative investment in a protocol that might gain adoption — it is the current infrastructure layer for agent-enabled applications across every major LLM provider. The 97 million download milestone is a trailing indicator of adoption that has already happened.
The development workflow is straightforward: initialize a TypeScript project, install the SDK, define your tools with Zod schemas and descriptive metadata, expose your data as resources, encode your workflows as prompt templates, and add authentication and rate limiting before deploying. Each component builds on standard TypeScript patterns that you already know. The protocol layer is what is new; the implementation logic is the same business code you have always written.
For teams evaluating where to start, the highest-value pattern is wrapping existing internal APIs as MCP tools. This gives your AI agents immediate access to your systems through a standardized protocol while preserving the existing API as the source of truth. From there, add resources for data access and prompts for workflow standardization. The developer guide to building and selling custom AI agents covers the business side of productizing these capabilities.
Ready to Build AI Agent Infrastructure?
Our development team builds custom MCP servers and AI agent integrations that connect your systems to every major LLM platform through a single standardized protocol.
Related Articles
Continue exploring with these related guides