Build MCP SDK
Build and maintain MCP servers using
@modelcontextprotocol/sdk
v1.x (single package, Zod-based, protocol version 2025-11-25). Covers
,
,
,
, transports, OAuth 2.1, sessions, and deployment.
When to use a different skill instead:
- Imports from
@modelcontextprotocol/server
(split packages) → use
- Handlers use instead of flat → use
- Uses the wrapper library → use
- Auditing/optimizing an existing server → use
How to detect v1: @modelcontextprotocol/sdk
(single package) in
. Handlers use
with
,
,
at the top level.
Core rules:
- Always use — the class is deprecated for direct use
- Always use / / — positional / / overloads are deprecated
- Always use for input/output schemas — the SDK converts them to JSON Schema 2020-12 automatically
- Always use
StreamableHTTPServerTransport
for HTTP — is deprecated
- Access only for sampling, elicitation, resource subscriptions, or custom protocol extensions
- Tool names SHOULD be 1-128 chars using letters, digits, underscore, hyphen, dot
- Input validation errors SHOULD be returned as tool execution errors () not protocol errors — enables LLM self-correction
Workflow
1 — Detect what exists
Run
and
ls package.json tsconfig.json
in the project directory. Look for:
@modelcontextprotocol/sdk
in dependencies → existing MCP server
- in dependencies → wrong skill, redirect to
- or key in → MCP client config, not server code
- with tool/resource handler files → existing implementation to extend
Summarize: existing server (go to Step 2A) or new server (go to Step 2B).
2A — Audit an existing SDK server
When an MCP server already exists, do not rebuild. Read the implementation and assess:
- Which API style is used? If deprecated / , migrate to /
- Are Zod schemas defined for all tool inputs? If raw JSON Schema objects, convert to Zod
- Is the transport current? If , migrate to
StreamableHTTPServerTransport
- Are tool annotations set? Add , , , where missing
- Does the server validate header for HTTP transport? Add or
- Does the server declare capabilities correctly during initialization? Check , , ,
Then proceed to the user's requested changes (add tools, fix bugs, add auth, etc.).
2B — Scope a new server
Ask or infer from context:
- What does the server wrap? (API, database, file system, CLI tool, etc.)
- Transport? stdio for local CLI integration, Streamable HTTP for remote/multi-client
- Auth needed? Bearer token, OAuth 2.1, or none (local stdio)
- Tools, resources, or prompts? Most servers need tools; resources for data access; prompts for reusable templates
- Client features needed? Sampling (LLM completions), elicitation (user input), roots (filesystem access)
3 — Choose the implementation branch
| Scenario | Action |
|---|
| New stdio server | Scaffold from quick-start template → references/guides/quick-start.md
|
| New HTTP server (stateful) | Scaffold with session management → references/guides/transports.md
|
| New HTTP server (stateless) | Scaffold without sessions → references/guides/transports.md
|
| Add tools to existing server | Read references/guides/tools-and-schemas.md
, register new tools |
| Add resources | Read references/guides/resources-and-prompts.md
|
| Add authentication | Read references/guides/authentication.md
|
| Add sampling or elicitation | Read references/guides/sessions-and-lifecycle.md
|
| Deploy to production | Read references/patterns/deployment.md
|
| Understand the MCP protocol | Read references/guides/protocol-spec.md
|
4 — Preflight setup
Before writing server code, confirm:
5 — Build or extend the server
Default implementation sequence:
- Create instance with name, version, and optional description/icons
- Define Zod schemas for each tool's input (and output if is needed)
- Register tools with — input schema, annotations, async handler
- Register resources with
server.registerResource()
if the server exposes data
- Register prompts with if the server provides templates
- Create transport and connect:
await server.connect(transport)
- Handle graceful shutdown with
process.on('SIGINT', ...)
Refer to
references/examples/server-recipes.md
for complete working examples.
6 — Validate
- stdio: Test with
npx @anthropic-ai/mcp-inspector
or pipe JSON-RPC messages directly
- HTTP: Start the server, then test with or the MCP Inspector
- Tool schemas: Verify Zod validation catches bad input (pass invalid args, confirm error response)
- Annotations: Check that / are accurate for each tool
- Capabilities: Verify the server declares the correct capabilities during initialization
Quick start — minimal stdio server
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: "my-server", version: "1.0.0" },
{ instructions: "A helpful server" }
);
server.registerTool("greet", {
description: "Greet a user by name",
inputSchema: { name: z.string().describe("The user's name") },
annotations: { readOnlyHint: true, destructiveHint: false },
}, async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}));
const transport = new StdioServerTransport();
await server.connect(transport);
Core API summary
McpServer
typescript
new McpServer(
{ name: string, version: string, description?: string, icons?: Icon[] },
{ capabilities?: ServerCapabilities, instructions?: string }
)
server.connect(transport: Transport): Promise<void>
server.close(): Promise<void>
server.registerTool(name, config, handler): RegisteredTool
server.registerResource(name, uri | template, config, handler): RegisteredResource
server.registerPrompt(name, config, handler): RegisteredPrompt
server.sendToolListChanged(): void
server.sendResourceListChanged(): void
server.sendPromptListChanged(): void
server.sendLoggingMessage(params): Promise<void>
registerTool config
typescript
{
title?: string, // Human-readable display name
description?: string, // LLM reads this to decide when to call the tool
inputSchema?: ZodRawShape | ZodSchema,
outputSchema?: ZodRawShape | ZodSchema, // Enables structuredContent validation
annotations?: {
readOnlyHint?: boolean, // Does not modify state
destructiveHint?: boolean, // May delete/modify data
idempotentHint?: boolean, // Repeated calls safe
openWorldHint?: boolean, // Interacts with external services
},
icons?: Icon[], // Visual identifier (2025-11-25)
}
CallToolResult
typescript
{
content: Array<
| { type: "text", text: string }
| { type: "image", data: string, mimeType: string }
| { type: "audio", data: string, mimeType: string }
| { type: "resource", resource: { uri: string, text?: string, blob?: string } }
| { type: "resource_link", uri: string, name?: string, description?: string }
>,
structuredContent?: Record<string, unknown>,
isError?: boolean,
}
RequestHandlerExtra (v1.x)
Every handler receives
as the last argument:
typescript
{
signal: AbortSignal, // For cooperative cancellation
authInfo?: AuthInfo, // From OAuth middleware
sessionId?: string, // Current session ID
requestId: RequestId, // JSON-RPC request ID
requestInfo?: RequestInfo, // Original HTTP request metadata
_meta?: RequestMeta, // Protocol-level metadata
sendNotification: (notification) => Promise<void>,
sendRequest: (request, schema, options?) => Promise<Result>,
}
Note: In v2 alpha, this becomes
with a restructured API. See
references/guides/v2-migration.md
.
Error handling
typescript
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
// Hard protocol errors (tool not found, bad params):
throw new McpError(ErrorCode.InvalidParams, "Missing required field: query");
// Soft tool errors (API failures the LLM can handle):
return { content: [{ type: "text", text: "Error: rate limit exceeded" }], isError: true };
Per spec: input validation errors SHOULD use
(tool execution errors) rather than protocol errors — this enables model self-correction.
Decision rules
- Prefer () for simple inputs — use full only for transforms, refinements, or discriminated unions
- Prefer soft errors over thrown for recoverable failures — LLMs handle soft errors better
- Prefer stdio for local-only servers — zero infrastructure, single client
- Prefer Streamable HTTP for remote or multi-client servers
- Prefer stateful HTTP (with ) when the server needs progress notifications, resumability, or multi-turn context
- Prefer stateless HTTP (
sessionIdGenerator: undefined
) for simple request-response tools
- Set on every tool — LLMs use them to decide execution safety; treat annotations as untrusted unless from a trusted server
- Use (the underlying ) only when lacks the method you need
- Use when the tool must return validated structured data alongside text content
- Tool names: use format (e.g. ), 1-128 characters
Guardrails
- Never use the deprecated , , or positional-argument methods
- Never use the deprecated in new servers
- Never use the deprecated class directly — always go through
- Never expose internal error details to clients — return user-friendly error messages
- Never skip schemas for tool inputs — unvalidated input is a security risk
- Never hardcode secrets — use environment variables for API keys and tokens
- Never omit graceful shutdown handling for HTTP servers
- Never run HTTP servers on localhost without DNS rebinding protection ( or middleware)
- Never use — for parameterless tools, omit entirely
- Servers MUST validate header on HTTP transport to prevent DNS rebinding; respond with 403 for invalid origins
Reference routing
Use the smallest relevant set for the branch of work.
Start here
| Reference | When to read |
|---|
references/guides/quick-start.md
| Scaffolding a new server from scratch |
references/guides/tools-and-schemas.md
| Registering tools, defining Zod schemas, handling tool results |
references/guides/transports.md
| Choosing and configuring stdio, Streamable HTTP, or SSE (legacy) |
Server capabilities
| Reference | When to read |
|---|
references/guides/resources-and-prompts.md
| Adding resources (static/template URI) or prompts |
references/guides/authentication.md
| Adding OAuth 2.1, bearer tokens, or custom auth |
references/guides/client-api.md
| Building MCP clients — connecting, calling tools, reading resources, auth, sampling |
references/guides/sessions-and-lifecycle.md
| Managing sessions, sampling, elicitation, resumability, graceful shutdown |
references/guides/experimental-tasks.md
| Durable long-running tool operations — registerToolTask, InMemoryTaskStore, callToolStream |
references/guides/protocol-spec.md
| Understanding protocol lifecycle, capabilities, message format, security requirements |
references/guides/v2-migration.md
| Planning for v2 alpha migration — package split, Standard Schema, ServerContext, framework adapters |
Build and ship
| Reference | When to read |
|---|
references/examples/server-recipes.md
| Copy-paste working server examples for common patterns |
references/patterns/deployment.md
| Deploying to production (Docker, serverless, cloud) |
references/patterns/production-patterns.md
| Logging, error handling, rate limiting, monitoring |
references/patterns/anti-patterns.md
| Common mistakes and how to fix them |
Specification Enhancement Proposals (SEPs)
| Reference | When to read |
|---|
references/seps/overview.md
| Understanding what SEPs exist and their developer impact |
references/seps/auth-security.md
| Implementing OAuth flows, enterprise auth, URL elicitation, client security |
references/seps/tools-metadata.md
| Tool naming rules, icons, validation errors, sampling with tools, tasks, tracing |
references/seps/protocol-transport.md
| JSON Schema dialect, SSE polling, extensions framework, elicitation improvements, MCP Apps |
references/seps/upcoming.md
| Accepted SEPs not yet Final — upcoming breaking changes to prepare for |
Why migrate to v2?
v2 (
@modelcontextprotocol/server
+
@modelcontextprotocol/client
) shipped in early 2026. Key benefits over v1:
- Structured handler context — , ,
ctx.mcpReq.requestSampling()
replace manual calls
- Framework adapters — and from dedicated packages instead of manual wiring
- Standard Schema support — use Zod v4, Valibot, or ArkType for tool schemas (not locked to Zod)
- JSON Schema 2020-12 by default — v1 defaults to Draft-7 via ; v2 uses native
- Better auth — client middleware system (, , ), grant-agnostic , discovery caching
- Smaller bundles — split packages mean you only install what you use
- ESM-only — cleaner module resolution, no CJS baggage
Community adoption is still early (Q1 2026 release). Most production servers remain on v1.x. See
references/guides/v2-migration.md
for the full source-verified migration guide, or use
for new v2 projects.
Compatibility note
This skill targets
@modelcontextprotocol/sdk
v1.x (stable,
branch). Source-verified against the TypeScript SDK repository.
Key 2025-11-25 spec additions: icons for tools/resources/prompts, tool name guidance (SEP-986), URL-mode elicitation (SEP-1036), tool calling in sampling (SEP-1577), experimental tasks (SEP-1686), JSON Schema 2020-12 as default dialect (SEP-1613), extensions framework (SEP-2133).