build-mcp-sdk
Original:🇺🇸 English
Translated
Use skill if you are building or maintaining MCP servers with @modelcontextprotocol/sdk v1.x — single-package TypeScript SDK with Zod, RequestHandlerExtra, and built-in OAuth.
2installs
Added on
NPX Install
npx skill4agent add yigitkonur/skills-by-yigitkonur build-mcp-sdkTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Build MCP SDK
Build and maintain MCP servers using v1.x (single package, Zod-based, protocol version 2025-11-25). Covers , , , , transports, OAuth 2.1, sessions, and deployment.
@modelcontextprotocol/sdkMcpServerregisterToolregisterResourceregisterPromptWhen to use a different skill instead:
- Imports from (split packages) → use
@modelcontextprotocol/serverbuild-mcp-sdk-v2 - Handlers use instead of flat
ctx.mcpReq→ useextrabuild-mcp-sdk-v2 - Uses the wrapper library → use
mcp-usebuild-mcp-use-server - Auditing/optimizing an existing server → use
optimize-mcp-server
How to detect v1: (single package) in . Handlers use with , , at the top level.
@modelcontextprotocol/sdkpackage.json(args, extra)extra.sendNotificationextra.authInfoextra.signalCore rules:
- Always use — the
McpServerclass is deprecated for direct useServer - Always use /
registerTool/registerResource— positionalregisterPrompt/tool()/resource()overloads are deprecatedprompt() - Always use for input/output schemas — the SDK converts them to JSON Schema 2020-12 automatically
zod - Always use for HTTP —
StreamableHTTPServerTransportis deprecatedSSEServerTransport - Access only for sampling, elicitation, resource subscriptions, or custom protocol extensions
server.server - 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
isError: true
Workflow
1 — Detect what exists
Run and in the project directory. Look for:
tree -L 3ls package.json tsconfig.json- in
@modelcontextprotocol/sdkdependencies → existing MCP serverpackage.json - in dependencies → wrong skill, redirect to
mcp-usebuild-mcp-use-server - or
.mcp.jsonkey inmcp→ MCP client config, not server codepackage.json - with tool/resource handler files → existing implementation to extend
src/
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 /
tool(), migrate toresource()/registerToolregisterResource - Are Zod schemas defined for all tool inputs? If raw JSON Schema objects, convert to Zod
- Is the transport current? If , migrate to
SSEServerTransportStreamableHTTPServerTransport - Are tool annotations set? Add ,
readOnlyHint,destructiveHint,idempotentHintwhere missingopenWorldHint - Does the server validate header for HTTP transport? Add
OriginorcreateMcpExpressApp()hostHeaderValidation - Does the server declare capabilities correctly during initialization? Check ,
tools,resources,promptslogging
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 → |
| New HTTP server (stateful) | Scaffold with session management → |
| New HTTP server (stateless) | Scaffold without sessions → |
| Add tools to existing server | Read |
| Add resources | Read |
| Add authentication | Read |
| Add sampling or elicitation | Read |
| Deploy to production | Read |
| Understand the MCP protocol | Read |
4 — Preflight setup
Before writing server code, confirm:
- Node.js 18+ installed (required for )
globalThis.crypto - — both are required
npm install @modelcontextprotocol/sdk zod - TypeScript 5+ with or
"moduleResolution": "node16"in tsconfig"nodenext" - If HTTP transport: (Express 5 recommended)
npm install express
5 — Build or extend the server
Default implementation sequence:
- Create instance with name, version, and optional description/icons
McpServer - Define Zod schemas for each tool's input (and output if is needed)
structuredContent - Register tools with — input schema, annotations, async handler
server.registerTool() - Register resources with if the server exposes data
server.registerResource() - Register prompts with if the server provides templates
server.registerPrompt() - Create transport and connect:
await server.connect(transport) - Handle graceful shutdown with
process.on('SIGINT', ...)
Refer to for complete working examples.
references/examples/server-recipes.md6 — Validate
- stdio: Test with or pipe JSON-RPC messages directly
npx @anthropic-ai/mcp-inspector - HTTP: Start the server, then test with or the MCP Inspector
curl - Tool schemas: Verify Zod validation catches bad input (pass invalid args, confirm error response)
- Annotations: Check that /
readOnlyHintare accurate for each tooldestructiveHint - 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:
extratypescript
{
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 .
ServerContextreferences/guides/v2-migration.mdError 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.
isError: trueDecision rules
- Prefer (
ZodRawShape) for simple inputs — use full{ name: z.string() }only for transforms, refinements, or discriminated unionsz.object() - Prefer soft errors over thrown
isError: truefor recoverable failures — LLMs handle soft errors betterMcpError - 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
sessionIdGenerator - Prefer stateless HTTP () for simple request-response tools
sessionIdGenerator: undefined - Set on every tool — LLMs use them to decide execution safety; treat annotations as untrusted unless from a trusted server
annotations - Use (the underlying
server.server) only whenServerlacks the method you needMcpServer - Use when the tool must return validated structured data alongside text content
outputSchema - Tool names: use format (e.g.
service_action_resource), 1-128 charactersgithub_search_repos
Guardrails
- Never use the deprecated ,
tool(), orresource()positional-argument methodsprompt() - Never use the deprecated in new servers
SSEServerTransport - Never use the deprecated class directly — always go through
ServerMcpServer - Never expose internal error details to clients — return user-friendly error messages
- Never skip schemas for tool inputs — unvalidated input is a security risk
zod - 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
createMcpExpressApp()middleware)hostHeaderValidation - Never use — for parameterless tools, omit
inputSchema: nullentirelyinputSchema - Servers MUST validate header on HTTP transport to prevent DNS rebinding; respond with 403 for invalid origins
Origin
Reference routing
Use the smallest relevant set for the branch of work.
Start here
| Reference | When to read |
|---|---|
| Scaffolding a new server from scratch |
| Registering tools, defining Zod schemas, handling tool results |
| Choosing and configuring stdio, Streamable HTTP, or SSE (legacy) |
Server capabilities
| Reference | When to read |
|---|---|
| Adding resources (static/template URI) or prompts |
| Adding OAuth 2.1, bearer tokens, or custom auth |
| Building MCP clients — connecting, calling tools, reading resources, auth, sampling |
| Managing sessions, sampling, elicitation, resumability, graceful shutdown |
| Durable long-running tool operations — registerToolTask, InMemoryTaskStore, callToolStream |
| Understanding protocol lifecycle, capabilities, message format, security requirements |
| Planning for v2 alpha migration — package split, Standard Schema, ServerContext, framework adapters |
Build and ship
| Reference | When to read |
|---|---|
| Copy-paste working server examples for common patterns |
| Deploying to production (Docker, serverless, cloud) |
| Logging, error handling, rate limiting, monitoring |
| Common mistakes and how to fix them |
Specification Enhancement Proposals (SEPs)
| Reference | When to read |
|---|---|
| Understanding what SEPs exist and their developer impact |
| Implementing OAuth flows, enterprise auth, URL elicitation, client security |
| Tool naming rules, icons, validation errors, sampling with tools, tasks, tracing |
| JSON Schema dialect, SSE polling, extensions framework, elicitation improvements, MCP Apps |
| Accepted SEPs not yet Final — upcoming breaking changes to prepare for |
Why migrate to v2?
v2 ( + ) shipped in early 2026. Key benefits over v1:
@modelcontextprotocol/server@modelcontextprotocol/client- Structured handler context — ,
ctx.mcpReq.log(),ctx.mcpReq.elicitInput()replace manualctx.mcpReq.requestSampling()callsextra.sendRequest() - Framework adapters — and
createMcpExpressApp()from dedicated packages instead of manual wiringcreateMcpHonoApp() - 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
zod-to-json-schemaz.toJSONSchema() - Better auth — client middleware system (,
withOAuth,withLogging), grant-agnosticapplyMiddlewares, discovery cachingprepareTokenRequest() - 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 for the full source-verified migration guide, or use for new v2 projects.
references/guides/v2-migration.mdbuild-mcp-sdk-v2Compatibility note
This skill targets v1.x (stable, branch). Source-verified against the TypeScript SDK repository.
@modelcontextprotocol/sdkv1.xKey 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).