Build MCP Server (SDK v1.x)
Build and maintain MCP servers using @modelcontextprotocol/sdk v1.x — the single-package, Zod-based TypeScript SDK (protocol version 2025-11-25). Covers McpServer, registerTool, registerResource, registerPrompt, transports, OAuth 2.1, sessions, and deployment.
When to use this skill
- Building a new MCP server on
@modelcontextprotocol/sdk v1.x (single package)
- Adding tools, resources, or prompts to an existing v1 server
- Migrating a v1 server from deprecated APIs (
tool(), SSEServerTransport, raw JSON Schema) to current ones (registerTool, StreamableHTTPServerTransport, Zod)
- Wiring authentication on a v1 server — bearer token, OAuth 2.1 via
mcpAuthRouter, or custom middleware
- Hardening transports, sessions, or capabilities on a v1 server (Origin validation, session resumability, sampling/elicitation)
- Diagnosing v1-specific runtime errors —
RequestHandlerExtra access, capability declarations, JSON Schema 2020-12 conversion
Do NOT use this skill when
- Project imports from
@modelcontextprotocol/server / @modelcontextprotocol/client / @modelcontextprotocol/node (split packages) → use build-mcp-server-sdk-v2
- Handlers receive
(args, ctx) with ctx.mcpReq.log() / ctx.http?.authInfo (v2 ServerContext) → use build-mcp-server-sdk-v2
- Goal is porting an existing v1 server to v2 (not new build, not v1 maintenance) → use
convert-mcp-sdk-v1-to-v2
- Project depends on the
mcp-use wrapper library, not the raw SDK → use build-mcp-use-server
- Goal is an agentic-quality / hardening / context-budget audit beyond SDK correctness → use
audit-agentic-mcp
Detect v1 vs v2 (do this first)
Before writing any code, confirm v1 by checking three signals. Any one v2 signal means stop and route to a different skill.
| Signal |
v1 (this skill) |
v2 (build-mcp-server-sdk-v2) |
package.json dependency |
@modelcontextprotocol/sdk (single, ^1.x) |
@modelcontextprotocol/server, /client, /node, /express, /hono (split, 2.0.0-alpha.x) |
| Import path |
@modelcontextprotocol/sdk/server/mcp.js, /server/stdio.js, /server/streamableHttp.js |
@modelcontextprotocol/server, @modelcontextprotocol/node |
| Handler signature |
(args, extra) => … with extra.sendNotification, extra.authInfo, extra.signal flat |
(args, ctx) => … with ctx.mcpReq.log(), ctx.mcpReq.signal, ctx.http?.authInfo |
| HTTP transport class |
StreamableHTTPServerTransport (or legacy SSEServerTransport) |
NodeStreamableHTTPServerTransport |
| Module system |
CJS or ESM |
ESM-only, "type": "module" required |
| Node engine |
Node 18+ |
Node 20+ |
| Zod |
Zod v3, ZodRawShape accepted ({ name: z.string() }) |
Zod v4, full z.object({...}) only |
Legacy low-level v1 code may also import request schemas like ListToolsRequestSchema, CallToolRequestSchema from @modelcontextprotocol/sdk/types.js and call server.setRequestHandler(...) directly. That is still v1 — but it is the deprecated low-level path; the skill recommends migrating it to McpServer.registerTool (see references/patterns/anti-patterns.md).
If package.json exists, run bash scripts/check-mcp-sdk-v1-version.sh [project-dir] (see scripts/check-mcp-sdk-v1-version.sh.md) — it asserts single-package v1 and refuses to run if v2 split packages are present.
Core rules
- Always use
McpServer from @modelcontextprotocol/sdk/server/mcp.js — the low-level Server class is deprecated for direct use
- Always use
registerTool / registerResource / registerPrompt — positional tool() / resource() / prompt() overloads are deprecated
- Always use
zod for input/output schemas — the SDK auto-converts to JSON Schema 2020-12
- Always use
StreamableHTTPServerTransport for HTTP — SSEServerTransport is deprecated
- Always set
annotations on tools (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) — LLMs rely on them for safe execution
- Tool names per SEP-986: 1–64 chars from
A–Z a–z 0–9 _ - . /; format service_action_resource (e.g. github_search_repos)
- Input validation failures SHOULD return
{ isError: true } (tool execution error, LLM-recoverable) — not thrown McpError (protocol error)
- Access
server.server (the underlying low-level Server) only for sampling, elicitation, resource subscriptions, or custom protocol extensions
Workflow
1 — Detect what exists
Run tree -L 3 and inspect package.json and tsconfig.json. Look for:
@modelcontextprotocol/sdk in dependencies → existing v1 server (go to Step 2A)
@modelcontextprotocol/server (split) → wrong skill, redirect to build-mcp-server-sdk-v2
mcp-use in dependencies → wrong skill, redirect to build-mcp-use-server
.mcp.json or top-level mcp key in package.json → MCP client config, not server code
src/ with tool handler files → existing implementation to extend
- Empty/greenfield → go to Step 2B
For existing projects, run bash scripts/check-mcp-sdk-v1-version.sh [project-dir] to confirm v1 single-package and zod are present.
2A — Audit an existing v1 server
When an MCP server already exists, do not rebuild. Read the implementation and assess each axis:
- API style: deprecated
tool() / resource() / setRequestHandler low-level → migrate to registerTool / registerResource
- Schemas: raw JSON Schema objects → convert to Zod (preserves type inference and JSON Schema generation)
- Transport:
SSEServerTransport → migrate to StreamableHTTPServerTransport
- Annotations: missing on any tool → add
readOnlyHint / destructiveHint / idempotentHint / openWorldHint
- Origin validation: HTTP transport without DNS-rebinding protection → add
createMcpExpressApp() or hostHeaderValidation middleware
- Capabilities: verify
tools, resources, prompts, logging declared correctly during initialization
Then proceed to the user's requested change (add tools, fix bugs, add auth, etc.).
2B — Scope a new v1 server
Ask or infer:
- What does the server wrap? API, database, file system, CLI tool
- Transport? stdio (local CLI), Streamable HTTP stateful (sessions, resumability), Streamable HTTP stateless (simple req/resp)
- Auth? None (local stdio), static bearer, OAuth 2.1, custom middleware
- Surfaces? Tools (most common), resources (data access), prompts (reusable templates)
- Client features? Sampling (LLM completions), elicitation (user input), roots (filesystem access)
For empty greenfield, scaffold with bash scripts/scaffold-v1-server.sh <target-dir> <server-name> [stdio|http-stateful|http-stateless] (see scripts/scaffold-v1-server.sh.md).
3 — Branch by scenario
| Scenario |
First read |
| New stdio server |
references/guides/quick-start.md |
| New HTTP server (stateful or stateless) |
references/guides/transports.md |
| Add tools to existing server |
references/guides/tools-and-schemas.md |
| Add resources or prompts |
references/guides/resources-and-prompts.md |
| Add authentication |
references/guides/authentication.md |
| Build a v1 client |
references/guides/client-api.md |
| Add sampling, elicitation, or session resumability |
references/guides/sessions-and-lifecycle.md |
| Long-running tools / durable tasks |
references/guides/experimental-tasks.md |
| Understand the MCP protocol contract |
references/guides/protocol-spec.md |
| Deploy to production |
references/patterns/deployment.md |
| Wire logging, error handling, rate limits, monitoring |
references/patterns/production-patterns.md |
| Avoid common v1 mistakes |
references/patterns/anti-patterns.md |
| Copy-paste working server example |
references/examples/server-recipes.md |
4 — Preflight
5 — Build sequence
- Create
McpServer instance with name, version, optional description and icons
- Define Zod schemas for each tool's input (and
outputSchema if returning structuredContent)
- Register tools with
server.registerTool(name, config, handler) — config carries schema, annotations, description
- Register resources with
server.registerResource() if exposing data
- Register prompts with
server.registerPrompt() if exposing templates
- Create transport and connect:
await server.connect(transport)
- Handle graceful shutdown:
process.on('SIGINT', async () => { await server.close(); process.exit(0); })
See references/examples/server-recipes.md for complete working examples by transport.
6 — Validate
- Local checks first:
npm run build, focused unit tests
- Live smoke test with the bundled
test-by-mcpc-cli skill, npx @anthropic-ai/mcp-inspector, or raw JSON-RPC. Minimum sequence: initialize/connect → tools/list → one successful tool call → one invalid-argument call returning isError: true
- Verify Zod catches bad input — pass invalid args, confirm
isError: true
- Verify annotations are accurate per tool
- Verify capabilities are declared (initialize response)
- For deeper hardening or agentic-quality audits, route to
audit-agentic-mcp — do not duplicate that here
Quick start — minimal stdio server
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}!` }],
}));
await server.connect(new StdioServerTransport());
Core API summary
McpServer
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
{
title?: string, // Human-readable display name
description?: string, // LLM reads this to decide when to call
inputSchema?: ZodRawShape | ZodSchema,
outputSchema?: ZodRawShape | ZodSchema, // Enables structuredContent validation
annotations?: {
readOnlyHint?: boolean,
destructiveHint?: boolean,
idempotentHint?: boolean,
openWorldHint?: boolean,
},
icons?: Icon[], // 2025-11-25
}
CallToolResult
{
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-specific — flat shape)
Every handler receives extra as the last argument:
{
signal: AbortSignal, // Cooperative cancellation
authInfo?: AuthInfo, // From OAuth middleware
sessionId?: string,
requestId: RequestId,
requestInfo?: RequestInfo, // Original HTTP request metadata
_meta?: RequestMeta,
sendNotification: (notification) => Promise<void>,
sendRequest: (request, schema, options?) => Promise<Result>,
}
This flat shape is the single biggest v1-vs-v2 tell. v2 nests these fields under ctx.mcpReq / ctx.http. To port an existing v1 server to v2, use convert-mcp-sdk-v1-to-v2.
Error handling
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
// Hard protocol errors (tool not found, bad params at the protocol layer):
throw new McpError(ErrorCode.InvalidParams, "Missing required field: query");
// Soft tool errors (recoverable; LLM can retry or self-correct):
return { content: [{ type: "text", text: "Error: rate limit exceeded" }], isError: true };
Per spec: input validation errors SHOULD use isError: true, not thrown McpError — soft errors enable model self-correction.
Decision rules
- Prefer
ZodRawShape ({ name: z.string() }) for simple inputs; use full z.object() only for transforms, refinements, discriminated unions
- Prefer
isError: true soft errors over thrown McpError for recoverable failures
- Prefer stdio for local-only servers (zero infra, single client)
- Prefer Streamable HTTP for remote or multi-client servers
- Prefer stateful HTTP (with
sessionIdGenerator) when the server needs progress notifications, resumability, or multi-turn context
- Prefer stateless HTTP (
sessionIdGenerator: undefined) for simple request-response tools
- Use
outputSchema when the tool returns validated structured data alongside text
- Tool names:
service_action_resource, 1–64 chars (SEP-986)
Guardrails
- Never use deprecated
tool(), resource(), prompt() positional methods
- Never use deprecated
SSEServerTransport in new servers
- Never use the
Server class directly — go through McpServer
- Never expose internal error details to clients — return user-friendly messages
- Never skip
zod schemas for tool inputs — unvalidated input is a security risk
- Never hardcode secrets — use environment variables
- Never omit graceful shutdown for HTTP servers
- Never run HTTP servers without DNS-rebinding protection —
Origin header MUST be validated (use createMcpExpressApp() or hostHeaderValidation); respond 403 for invalid origins
- Never set
inputSchema: null — for parameterless tools, omit inputSchema entirely
Output contract
When work completes, report:
- Target path; new build vs existing-server maintenance
- SDK package and version range from
package.json
- Transport(s): stdio, stateful Streamable HTTP, stateless Streamable HTTP
- Tool / resource / prompt counts and names
- Auth mode: none, static bearer, OAuth 2.1, custom middleware
- Validation actually run and verification rung reached
- Publish/deploy path: npm
bin/npx command for stdio; HTTP endpoint path for remote; Docker/serverless note when applicable
server-info:
{
"name": "example-server",
"sdk": "@modelcontextprotocol/sdk@^1.x",
"transports": ["stdio"],
"tools": 3,
"resources": 0,
"prompts": 0,
"auth": "none",
"validatedWith": ["build", "test-by-mcpc-cli"]
}
Reference routing
Read only what the current branch needs. The full set:
Bundled scripts
| Script |
When to run |
scripts/check-mcp-sdk-v1-version.sh |
Existing-project preflight; asserts single-package v1 SDK and zod present, refuses if v2 split packages found. See scripts/check-mcp-sdk-v1-version.sh.md. |
scripts/scaffold-v1-server.sh |
Empty greenfield target after picking stdio, http-stateful, or http-stateless. See scripts/scaffold-v1-server.sh.md. |
Start-here guides
| 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 or template URI) or prompts |
references/guides/authentication.md |
OAuth 2.1, bearer tokens, custom middleware |
references/guides/client-api.md |
Building MCP clients — connecting, calling tools, reading resources, auth, sampling |
references/guides/sessions-and-lifecycle.md |
Sessions, sampling, elicitation, resumability, graceful shutdown |
references/guides/experimental-tasks.md |
Durable long-running operations — registerToolTask, InMemoryTaskStore, callToolStream |
references/guides/protocol-spec.md |
Protocol lifecycle, capabilities, message format, security requirements |
Build and ship
| Reference |
When to read |
references/examples/server-recipes.md |
Copy-paste working server examples by transport |
references/patterns/deployment.md |
Docker, serverless, cloud deployment |
references/patterns/production-patterns.md |
Logging, error handling, rate limiting, monitoring |
references/patterns/anti-patterns.md |
Common v1 mistakes and fixes |
Specification Enhancement Proposals (SEPs)
| Reference |
When to read |
references/seps/overview.md |
What SEPs exist and their developer impact |
references/seps/auth-security.md |
OAuth flows, enterprise auth, URL elicitation, client security |
references/seps/tools-metadata.md |
Tool naming (SEP-986), icons, validation errors, sampling-with-tools, tasks, tracing |
references/seps/protocol-transport.md |
JSON Schema 2020-12 dialect, SSE polling, extensions, MCP Apps, elicitation improvements |
references/seps/upcoming.md |
Accepted SEPs not yet Final — upcoming breaking changes to prepare for |
Compatibility note
This skill targets @modelcontextprotocol/sdk v1.x (stable, v1.x 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 default dialect (SEP-1613), extensions framework (SEP-2133).
v2 (@modelcontextprotocol/server + /client) remains pre-release alpha. Do not silently mix v1 and v2 packages. To port v1 → v2, route to convert-mcp-sdk-v1-to-v2. To start fresh on v2 alpha, route to build-mcp-server-sdk-v2.
1---2name: build-mcp-server-sdk-v13description: Use if building a TypeScript MCP server on @modelcontextprotocol/sdk v1.x — single-package, Zod.4---56# Build MCP Server (SDK v1.x)78Build and maintain MCP servers using `@modelcontextprotocol/sdk` v1.x — the **single-package**, Zod-based TypeScript SDK (protocol version 2025-11-25). Covers `McpServer`, `registerTool`, `registerResource`, `registerPrompt`, transports, OAuth 2.1, sessions, and deployment.910## When to use this skill1112- *Building a new MCP server on `@modelcontextprotocol/sdk` v1.x (single package)*13- *Adding tools, resources, or prompts to an existing v1 server*14- *Migrating a v1 server from deprecated APIs (`tool()`, `SSEServerTransport`, raw JSON Schema) to current ones (`registerTool`, `StreamableHTTPServerTransport`, Zod)*15- *Wiring authentication on a v1 server — bearer token, OAuth 2.1 via `mcpAuthRouter`, or custom middleware*16- *Hardening transports, sessions, or capabilities on a v1 server (Origin validation, session resumability, sampling/elicitation)*17- *Diagnosing v1-specific runtime errors — `RequestHandlerExtra` access, capability declarations, JSON Schema 2020-12 conversion*1819## Do NOT use this skill when2021- *Project imports from `@modelcontextprotocol/server` / `@modelcontextprotocol/client` / `@modelcontextprotocol/node` (split packages)* → use `build-mcp-server-sdk-v2`22- *Handlers receive `(args, ctx)` with `ctx.mcpReq.log()` / `ctx.http?.authInfo`* (v2 `ServerContext`) → use `build-mcp-server-sdk-v2`23- *Goal is **porting** an existing v1 server to v2 (not new build, not v1 maintenance)* → use `convert-mcp-sdk-v1-to-v2`24- *Project depends on the `mcp-use` wrapper library, not the raw SDK* → use `build-mcp-use-server`25- *Goal is an agentic-quality / hardening / context-budget audit beyond SDK correctness* → use `audit-agentic-mcp`2627## Detect v1 vs v2 (do this first)2829Before writing any code, confirm v1 by checking three signals. Any one v2 signal means stop and route to a different skill.3031| Signal | v1 (this skill) | v2 (`build-mcp-server-sdk-v2`) |32|---|---|---|33| `package.json` dependency | `@modelcontextprotocol/sdk` (single, `^1.x`) | `@modelcontextprotocol/server`, `/client`, `/node`, `/express`, `/hono` (split, `2.0.0-alpha.x`) |34| Import path | `@modelcontextprotocol/sdk/server/mcp.js`, `/server/stdio.js`, `/server/streamableHttp.js` | `@modelcontextprotocol/server`, `@modelcontextprotocol/node` |35| Handler signature | `(args, extra) => …` with `extra.sendNotification`, `extra.authInfo`, `extra.signal` flat | `(args, ctx) => …` with `ctx.mcpReq.log()`, `ctx.mcpReq.signal`, `ctx.http?.authInfo` |36| HTTP transport class | `StreamableHTTPServerTransport` (or legacy `SSEServerTransport`) | `NodeStreamableHTTPServerTransport` |37| Module system | CJS or ESM | ESM-only, `"type": "module"` required |38| Node engine | Node 18+ | Node 20+ |39| Zod | Zod v3, `ZodRawShape` accepted (`{ name: z.string() }`) | Zod v4, full `z.object({...})` only |4041Legacy low-level v1 code may also import request schemas like `ListToolsRequestSchema`, `CallToolRequestSchema` from `@modelcontextprotocol/sdk/types.js` and call `server.setRequestHandler(...)` directly. That is still v1 — but it is the deprecated low-level path; the skill recommends migrating it to `McpServer.registerTool` (see `references/patterns/anti-patterns.md`).4243If `package.json` exists, run `bash scripts/check-mcp-sdk-v1-version.sh [project-dir]` (see `scripts/check-mcp-sdk-v1-version.sh.md`) — it asserts single-package v1 and refuses to run if v2 split packages are present.4445## Core rules4647- Always use `McpServer` from `@modelcontextprotocol/sdk/server/mcp.js` — the low-level `Server` class is deprecated for direct use48- Always use `registerTool` / `registerResource` / `registerPrompt` — positional `tool()` / `resource()` / `prompt()` overloads are deprecated49- Always use `zod` for input/output schemas — the SDK auto-converts to JSON Schema 2020-1250- Always use `StreamableHTTPServerTransport` for HTTP — `SSEServerTransport` is deprecated51- Always set `annotations` on tools (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) — LLMs rely on them for safe execution52- Tool names per SEP-986: 1–64 chars from `A–Z a–z 0–9 _ - . /`; format `service_action_resource` (e.g. `github_search_repos`)53- Input validation failures SHOULD return `{ isError: true }` (tool execution error, LLM-recoverable) — not thrown `McpError` (protocol error)54- Access `server.server` (the underlying low-level `Server`) only for sampling, elicitation, resource subscriptions, or custom protocol extensions5556## Workflow5758### 1 — Detect what exists5960Run `tree -L 3` and inspect `package.json` and `tsconfig.json`. Look for:6162- `@modelcontextprotocol/sdk` in dependencies → existing v1 server (go to Step 2A)63- `@modelcontextprotocol/server` (split) → wrong skill, redirect to `build-mcp-server-sdk-v2`64- `mcp-use` in dependencies → wrong skill, redirect to `build-mcp-use-server`65- `.mcp.json` or top-level `mcp` key in `package.json` → MCP **client** config, not server code66- `src/` with tool handler files → existing implementation to extend67- Empty/greenfield → go to Step 2B6869For existing projects, run `bash scripts/check-mcp-sdk-v1-version.sh [project-dir]` to confirm v1 single-package and `zod` are present.7071### 2A — Audit an existing v1 server7273When an MCP server already exists, do not rebuild. Read the implementation and assess each axis:7475- **API style:** deprecated `tool()` / `resource()` / `setRequestHandler` low-level → migrate to `registerTool` / `registerResource`76- **Schemas:** raw JSON Schema objects → convert to Zod (preserves type inference and JSON Schema generation)77- **Transport:** `SSEServerTransport` → migrate to `StreamableHTTPServerTransport`78- **Annotations:** missing on any tool → add `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`79- **Origin validation:** HTTP transport without DNS-rebinding protection → add `createMcpExpressApp()` or `hostHeaderValidation` middleware80- **Capabilities:** verify `tools`, `resources`, `prompts`, `logging` declared correctly during initialization8182Then proceed to the user's requested change (add tools, fix bugs, add auth, etc.).8384### 2B — Scope a new v1 server8586Ask or infer:87881. **What does the server wrap?** API, database, file system, CLI tool892. **Transport?** stdio (local CLI), Streamable HTTP stateful (sessions, resumability), Streamable HTTP stateless (simple req/resp)903. **Auth?** None (local stdio), static bearer, OAuth 2.1, custom middleware914. **Surfaces?** Tools (most common), resources (data access), prompts (reusable templates)925. **Client features?** Sampling (LLM completions), elicitation (user input), roots (filesystem access)9394For empty greenfield, scaffold with `bash scripts/scaffold-v1-server.sh <target-dir> <server-name> [stdio|http-stateful|http-stateless]` (see `scripts/scaffold-v1-server.sh.md`).9596### 3 — Branch by scenario9798| Scenario | First read |99|---|---|100| New stdio server | `references/guides/quick-start.md` |101| New HTTP server (stateful or stateless) | `references/guides/transports.md` |102| Add tools to existing server | `references/guides/tools-and-schemas.md` |103| Add resources or prompts | `references/guides/resources-and-prompts.md` |104| Add authentication | `references/guides/authentication.md` |105| Build a v1 client | `references/guides/client-api.md` |106| Add sampling, elicitation, or session resumability | `references/guides/sessions-and-lifecycle.md` |107| Long-running tools / durable tasks | `references/guides/experimental-tasks.md` |108| Understand the MCP protocol contract | `references/guides/protocol-spec.md` |109| Deploy to production | `references/patterns/deployment.md` |110| Wire logging, error handling, rate limits, monitoring | `references/patterns/production-patterns.md` |111| Avoid common v1 mistakes | `references/patterns/anti-patterns.md` |112| Copy-paste working server example | `references/examples/server-recipes.md` |113114### 4 — Preflight115116- [ ] Node.js 18+ (required for `globalThis.crypto`)117- [ ] `npm install @modelcontextprotocol/sdk zod` — both required118- [ ] TypeScript 5+ with `"moduleResolution": "node16"` or `"nodenext"`119- [ ] HTTP transport: also `npm install express` (Express 5 recommended)120- [ ] Existing project passed `scripts/check-mcp-sdk-v1-version.sh`121122### 5 — Build sequence1231241. Create `McpServer` instance with `name`, `version`, optional `description` and `icons`1252. Define Zod schemas for each tool's input (and `outputSchema` if returning `structuredContent`)1263. Register tools with `server.registerTool(name, config, handler)` — config carries schema, annotations, description1274. Register resources with `server.registerResource()` if exposing data1285. Register prompts with `server.registerPrompt()` if exposing templates1296. Create transport and connect: `await server.connect(transport)`1307. Handle graceful shutdown: `process.on('SIGINT', async () => { await server.close(); process.exit(0); })`131132See `references/examples/server-recipes.md` for complete working examples by transport.133134### 6 — Validate1351361. Local checks first: `npm run build`, focused unit tests1372. **Live smoke test** with the bundled `test-by-mcpc-cli` skill, `npx @anthropic-ai/mcp-inspector`, or raw JSON-RPC. Minimum sequence: initialize/connect → `tools/list` → one successful tool call → one invalid-argument call returning `isError: true`1383. Verify Zod catches bad input — pass invalid args, confirm `isError: true`1394. Verify annotations are accurate per tool1405. Verify capabilities are declared (initialize response)1416. For deeper hardening or agentic-quality audits, route to `audit-agentic-mcp` — do not duplicate that here142143## Quick start — minimal stdio server144145```typescript146import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";147import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";148import { z } from "zod";149150const server = new McpServer(151 { name: "my-server", version: "1.0.0" },152 { instructions: "A helpful server" }153);154155server.registerTool("greet", {156 description: "Greet a user by name",157 inputSchema: { name: z.string().describe("The user's name") },158 annotations: { readOnlyHint: true, destructiveHint: false },159}, async ({ name }) => ({160 content: [{ type: "text", text: `Hello, ${name}!` }],161}));162163await server.connect(new StdioServerTransport());164```165166## Core API summary167168### McpServer169170```typescript171new McpServer(172 { name: string, version: string, description?: string, icons?: Icon[] },173 { capabilities?: ServerCapabilities, instructions?: string }174)175176server.connect(transport: Transport): Promise<void>177server.close(): Promise<void>178server.registerTool(name, config, handler): RegisteredTool179server.registerResource(name, uri | template, config, handler): RegisteredResource180server.registerPrompt(name, config, handler): RegisteredPrompt181server.sendToolListChanged(): void182server.sendResourceListChanged(): void183server.sendPromptListChanged(): void184server.sendLoggingMessage(params): Promise<void>185```186187### registerTool config188189```typescript190{191 title?: string, // Human-readable display name192 description?: string, // LLM reads this to decide when to call193 inputSchema?: ZodRawShape | ZodSchema,194 outputSchema?: ZodRawShape | ZodSchema, // Enables structuredContent validation195 annotations?: {196 readOnlyHint?: boolean,197 destructiveHint?: boolean,198 idempotentHint?: boolean,199 openWorldHint?: boolean,200 },201 icons?: Icon[], // 2025-11-25202}203```204205### CallToolResult206207```typescript208{209 content: Array<210 | { type: "text", text: string }211 | { type: "image", data: string, mimeType: string }212 | { type: "audio", data: string, mimeType: string }213 | { type: "resource", resource: { uri: string, text?: string, blob?: string } }214 | { type: "resource_link", uri: string, name?: string, description?: string }215 >,216 structuredContent?: Record<string, unknown>,217 isError?: boolean,218}219```220221### RequestHandlerExtra (v1-specific — flat shape)222223Every handler receives `extra` as the last argument:224225```typescript226{227 signal: AbortSignal, // Cooperative cancellation228 authInfo?: AuthInfo, // From OAuth middleware229 sessionId?: string,230 requestId: RequestId,231 requestInfo?: RequestInfo, // Original HTTP request metadata232 _meta?: RequestMeta,233 sendNotification: (notification) => Promise<void>,234 sendRequest: (request, schema, options?) => Promise<Result>,235}236```237238This flat shape is the **single biggest v1-vs-v2 tell**. v2 nests these fields under `ctx.mcpReq` / `ctx.http`. To port an existing v1 server to v2, use `convert-mcp-sdk-v1-to-v2`.239240### Error handling241242```typescript243import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";244245// Hard protocol errors (tool not found, bad params at the protocol layer):246throw new McpError(ErrorCode.InvalidParams, "Missing required field: query");247248// Soft tool errors (recoverable; LLM can retry or self-correct):249return { content: [{ type: "text", text: "Error: rate limit exceeded" }], isError: true };250```251252Per spec: input validation errors SHOULD use `isError: true`, not thrown `McpError` — soft errors enable model self-correction.253254## Decision rules255256- Prefer `ZodRawShape` (`{ name: z.string() }`) for simple inputs; use full `z.object()` only for transforms, refinements, discriminated unions257- Prefer `isError: true` soft errors over thrown `McpError` for recoverable failures258- Prefer stdio for local-only servers (zero infra, single client)259- Prefer Streamable HTTP for remote or multi-client servers260- Prefer **stateful** HTTP (with `sessionIdGenerator`) when the server needs progress notifications, resumability, or multi-turn context261- Prefer **stateless** HTTP (`sessionIdGenerator: undefined`) for simple request-response tools262- Use `outputSchema` when the tool returns validated structured data alongside text263- Tool names: `service_action_resource`, 1–64 chars (SEP-986)264265## Guardrails266267- Never use deprecated `tool()`, `resource()`, `prompt()` positional methods268- Never use deprecated `SSEServerTransport` in new servers269- Never use the `Server` class directly — go through `McpServer`270- Never expose internal error details to clients — return user-friendly messages271- Never skip `zod` schemas for tool inputs — unvalidated input is a security risk272- Never hardcode secrets — use environment variables273- Never omit graceful shutdown for HTTP servers274- Never run HTTP servers without DNS-rebinding protection — `Origin` header MUST be validated (use `createMcpExpressApp()` or `hostHeaderValidation`); respond 403 for invalid origins275- Never set `inputSchema: null` — for parameterless tools, omit `inputSchema` entirely276277## Output contract278279When work completes, report:280281- Target path; new build vs existing-server maintenance282- SDK package and version range from `package.json`283- Transport(s): stdio, stateful Streamable HTTP, stateless Streamable HTTP284- Tool / resource / prompt counts and names285- Auth mode: none, static bearer, OAuth 2.1, custom middleware286- Validation actually run and verification rung reached287- Publish/deploy path: npm `bin`/`npx` command for stdio; HTTP endpoint path for remote; Docker/serverless note when applicable288- `server-info`:289290```json291{292 "name": "example-server",293 "sdk": "@modelcontextprotocol/sdk@^1.x",294 "transports": ["stdio"],295 "tools": 3,296 "resources": 0,297 "prompts": 0,298 "auth": "none",299 "validatedWith": ["build", "test-by-mcpc-cli"]300}301```302303## Reference routing304305Read only what the current branch needs. The full set:306307### Bundled scripts308309| Script | When to run |310|---|---|311| `scripts/check-mcp-sdk-v1-version.sh` | Existing-project preflight; asserts single-package v1 SDK and `zod` present, refuses if v2 split packages found. See `scripts/check-mcp-sdk-v1-version.sh.md`. |312| `scripts/scaffold-v1-server.sh` | Empty greenfield target after picking `stdio`, `http-stateful`, or `http-stateless`. See `scripts/scaffold-v1-server.sh.md`. |313314### Start-here guides315316| Reference | When to read |317|---|---|318| `references/guides/quick-start.md` | Scaffolding a new server from scratch |319| `references/guides/tools-and-schemas.md` | Registering tools, defining Zod schemas, handling tool results |320| `references/guides/transports.md` | Choosing and configuring stdio, Streamable HTTP, or SSE (legacy) |321322### Server capabilities323324| Reference | When to read |325|---|---|326| `references/guides/resources-and-prompts.md` | Adding resources (static or template URI) or prompts |327| `references/guides/authentication.md` | OAuth 2.1, bearer tokens, custom middleware |328| `references/guides/client-api.md` | Building MCP clients — connecting, calling tools, reading resources, auth, sampling |329| `references/guides/sessions-and-lifecycle.md` | Sessions, sampling, elicitation, resumability, graceful shutdown |330| `references/guides/experimental-tasks.md` | Durable long-running operations — `registerToolTask`, `InMemoryTaskStore`, `callToolStream` |331| `references/guides/protocol-spec.md` | Protocol lifecycle, capabilities, message format, security requirements |332333### Build and ship334335| Reference | When to read |336|---|---|337| `references/examples/server-recipes.md` | Copy-paste working server examples by transport |338| `references/patterns/deployment.md` | Docker, serverless, cloud deployment |339| `references/patterns/production-patterns.md` | Logging, error handling, rate limiting, monitoring |340| `references/patterns/anti-patterns.md` | Common v1 mistakes and fixes |341342### Specification Enhancement Proposals (SEPs)343344| Reference | When to read |345|---|---|346| `references/seps/overview.md` | What SEPs exist and their developer impact |347| `references/seps/auth-security.md` | OAuth flows, enterprise auth, URL elicitation, client security |348| `references/seps/tools-metadata.md` | Tool naming (SEP-986), icons, validation errors, sampling-with-tools, tasks, tracing |349| `references/seps/protocol-transport.md` | JSON Schema 2020-12 dialect, SSE polling, extensions, MCP Apps, elicitation improvements |350| `references/seps/upcoming.md` | Accepted SEPs not yet Final — upcoming breaking changes to prepare for |351352## Compatibility note353354This skill targets `@modelcontextprotocol/sdk` v1.x (stable, `v1.x` branch). Source-verified against the TypeScript SDK repository.355356Key 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 default dialect (SEP-1613), extensions framework (SEP-2133).357358v2 (`@modelcontextprotocol/server` + `/client`) remains pre-release alpha. Do not silently mix v1 and v2 packages. To port v1 → v2, route to `convert-mcp-sdk-v1-to-v2`. To start fresh on v2 alpha, route to `build-mcp-server-sdk-v2`.