mrkeyoor.com_
Wed 05 Aug 05:03 UTC
npmAI / MLupdated 05 Aug 2026

@modelcontextprotocol/sdk

The official TypeScript SDK for the Model Context Protocol, the standard that lets AI applications (Claude, Cursor, VS Code, and most agent frameworks) talk to external tools, resources, and prompts through a common wire format. You use it to build MCP servers that expose your tools to any MCP client, or MCP clients that consume them, over stdio or Streamable HTTP. Important context: this package is the v1 line. The project's main branch is now v2, which splits the SDK into @modelcontextprotocol/server and @modelcontextprotocol/client, targeting the 2026-07-28 spec.

Verdict

The canonical way TypeScript speaks MCP, maintained by the protocol's own team, but the package is now the previous generation. Keep it for existing 1.x servers; start anything new on the v2 split packages.

API stability3/51.x accumulated deprecations (tool() vs registerTool, SSE vs Streamable HTTP) and the jump to v2 is a package split with a new schema layer; the SDK majors track a spec that still changes twice a year.
Docs4/5A dedicated docs site with step-by-step first-server and first-client tutorials, an API reference, troubleshooting page, and runnable self-verifying examples; the v1/v2 split means you must constantly check which generation a page describes.
Maintenance5/5Backed by the MCP steering group with Anthropic involvement, pushed to daily, and an explicit support policy: v1.x gets bug and security fixes for at least 6 months after v2's release.
Ecosystem5/5MCP is the de facto tool protocol for AI apps; 54M weekly downloads, and every major client (Claude, VS Code, Cursor) plus thousands of published servers interoperate with servers built on this SDK.

Use it if

  • You maintain an existing MCP server or client built on the 1.x API and need continued bug and security fixes, which the team has committed to for at least 6 months after the v2 release
  • You depend on tooling, templates, or hosting platforms that still expect the single @modelcontextprotocol/sdk package and its 1.x import paths
  • You want the reference implementation of the protocol in TypeScript, with both server and client sides, stdio and Streamable HTTP transports, and OAuth helpers in one package
  • You are pinned to Zod-shaped tool schemas that 1.x uses directly (v2 moved to Standard Schema, which accepts Zod v4, Valibot, or ArkType)
Skip it if

Setup reality

npm install @modelcontextprotocol/sdk zod and you can have a stdio server running in 20 lines; Node 18+ required and zod (v3.25+ or v4) is a peer dependency you install yourself. The annoyances: all imports use explicit .js subpaths (@modelcontextprotocol/sdk/server/mcp.js) which trips up TypeScript configs without moduleResolution node16/bundler; the package drags in a large dependency set including express and hono whether you use HTTP or not; Streamable HTTP session management (session IDs, resumability, DNS-rebinding protection) is your code to wire; and with v2 now published, a growing share of docs, examples, and blog posts show APIs that do not exist in 1.x.

Patterns

Minimal stdio MCP server (1.x API)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: "demo", version: "1.0.0" });

server.registerTool("add",
  {
    description: "Add two numbers",
    inputSchema: { a: z.number(), b: z.number() },
  },
  async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })
);

await server.connect(new StdioServerTransport());

In 1.x, inputSchema is a plain object of Zod fields (a raw shape), not z.object(...); v2 takes a full Standard Schema object instead.

Tool that returns structured contentstructured-output

server.registerTool("get-weather",
  {
    description: "Weather for a city",
    inputSchema: { city: z.string() },
    outputSchema: { temperature: z.number(), conditions: z.string() },
  },
  async ({ city }) => {
    const output = { temperature: 22.5, conditions: "sunny" };
    return {
      content: [{ type: "text", text: JSON.stringify(output) }],
      structuredContent: output,
    };
  }
);

When outputSchema is declared, you must return structuredContent that validates against it; keep the text content as a JSON mirror for older clients.

Expose a resource with a URI templateregister-resource

import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";

server.registerResource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  { title: "Greeting", description: "Personal greeting" },
  async (uri, { name }) => ({
    contents: [{ uri: uri.href, text: `Hello, ${name}!` }],
  })
);

Resources are for data the client chooses to read; if the model should decide to fetch it during a conversation, make it a tool instead.

Reusable prompt templateregister-prompt

server.registerPrompt("review-code",
  {
    title: "Code Review",
    description: "Review code for issues",
    argsSchema: { code: z.string() },
  },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: { type: "text", text: `Please review this code:\n\n${code}` },
    }],
  })
);

Prompts surface as user-invoked commands (slash commands in many clients), not as things the model calls on its own.

Stateless Streamable HTTP server with Expresshttp-stateless

import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
  const server = buildServer(); // fresh McpServer per request
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined, // stateless mode
  });
  res.on("close", () => { transport.close(); server.close(); });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000);

sessionIdGenerator: undefined disables sessions entirely; create a new server and transport per request or concurrent clients will collide.

Session-aware Streamable HTTP (multi-request clients)http-sessions

import { randomUUID } from "node:crypto";

const transports = {};

// inside app.post("/mcp", ...):
const sessionId = req.headers["mcp-session-id"];
let transport = sessionId ? transports[sessionId] : undefined;
if (!transport) {
  transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => randomUUID(),
    onsessioninitialized: (id) => { transports[id] = transport; },
  });
  await server.connect(transport);
}
await transport.handleRequest(req, res, req.body);

You own the session store and its cleanup; also enable enableDnsRebindingProtection and allowedHosts for locally exposed servers.

Client: spawn a stdio server and call a toolclient-stdio

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"],
});
const client = new Client({ name: "my-client", version: "1.0.0" });
await client.connect(transport);

const tools = await client.listTools();
const result = await client.callTool({
  name: "add",
  arguments: { a: 1, b: 2 },
});

StdioClientTransport spawns the server as a child process; the server must never write logs to stdout or it corrupts the JSON-RPC stream (use stderr).

Client: connect to a remote server over Streamable HTTPclient-http

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-client", version: "1.0.0" });
await client.connect(
  new StreamableHTTPClientTransport(new URL("https://example.com/mcp"))
);

Streamable HTTP replaced the older SSE transport; for servers that still speak SSE only, 1.x keeps SSEClientTransport as a deprecated fallback.

Return a tool error the model can readtool-error

server.registerTool("fetch-user",
  { description: "Fetch a user", inputSchema: { id: z.string() } },
  async ({ id }) => {
    const user = await db.find(id);
    if (!user) {
      return {
        isError: true,
        content: [{ type: "text", text: `No user with id ${id}` }],
      };
    }
    return { content: [{ type: "text", text: JSON.stringify(user) }] };
  }
);

Throwing raises a protocol-level error; returning isError: true keeps the failure inside the tool result so the model can recover from it.

Enable, disable, and update tools after connectdynamic-tools

const adminTool = server.registerTool("delete-user",
  { description: "Delete a user", inputSchema: { id: z.string() } },
  handler
);

adminTool.disable(); // hidden from listTools
// later, after auth upgrade:
adminTool.enable();  // emits listChanged notification automatically

registerTool returns a handle; enable/disable/update automatically send tools/listChanged so connected clients refresh.

Send log messages to the clientserver-logging

const server = new McpServer(
  { name: "demo", version: "1.0.0" },
  { capabilities: { logging: {} } }
);

// inside a tool handler:
await server.server.sendLoggingMessage({
  level: "info",
  data: "processing started",
});

Declare the logging capability at construction; console.log is not an option on stdio transports because stdout carries the protocol.

Alternatives

PackageRegistryPick it when
@modelcontextprotocol/servernpmNew MCP servers should start here: the v2 stable line for the 2026-07-28 spec, with Standard Schema support
fastmcpnpmYou want a higher-level TypeScript framework with sessions, auth, and CLI testing conveniences on top of MCP
mcpPyPIYour tools live in Python; this is the official Python SDK with the FastMCP decorator API built in