docs: rebuild the v2 documentation around a task-based page tree (#2397)

This commit is contained in:
Felix Weinberger 2026-06-30 21:18:42 +01:00 • committed by GitHub
commit b153505428
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 11884 additions and 4971 deletions

View file

@ -0,0 +1,9 @@
---
shape: reference
---
# Examples
Looking for code that shows how to do something specific? Browse the [example library](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples) — every example is a complete client–server pair that CI runs on every pull request, so they stay correct as the SDK changes. Or [suggest an addition](https://github.com/modelcontextprotocol/typescript-sdk/issues/new?template=v2-feedback.yml).
[`cli-client/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/cli-client) and [`todos-server/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/todos-server) are the reference pair — a full LLM chat host and the server it drives — if you want to see everything working together in one place.

View file

@ -0,0 +1,157 @@
---
shape: tutorial
---
# Build your first client
Build an MCP **client** — the program that launches a server, lists its tools, and calls them — against the weather server from [Build your first server](./first-server.md).
## Connect to a server
In the weather project, add the client package — it ships separately from `@modelcontextprotocol/server`.
```sh
npm install @modelcontextprotocol/client
```
Create `src/client.ts`. A `Client` plus one transport is a complete MCP client.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_connect"
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
const client = new Client({ name: 'my-first-client', version: '1.0.0' });
const transport = new StdioClientTransport({
command: 'npx',
args: ['tsx', 'src/index.ts']
});
await client.connect(transport);
```
`connect()` spawns `npx tsx src/index.ts` as a child process, speaks JSON-RPC over its stdin and stdout, and completes the **initialize** handshake. The client owns that process from here: it lives exactly as long as the transport.
::: tip
Never start `src/index.ts` yourself — `connect()` does. A `spawn npx ENOENT` error here means `command` is not an executable on your `PATH`.
:::
stdio is the transport local hosts use; [Connect to a server](../clients/connect.md) covers HTTP for remote servers.
## List the server's tools
`listTools` returns every tool the server registered, with the JSON Schema it derived for each one's arguments.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_listTools"
const { tools } = await client.listTools();
for (const tool of tools) {
console.log(tool.name, '—', tool.description);
}
```
Run what you have so far — `npx tsx src/client.ts` from the project root. The first line is the server's banner, forwarded from the child's stderr; the second is your loop.
```text
weather MCP server running on stdio
get-alerts — Get the active weather alerts for a US state
```
The script does not exit on its own — the client still owns a live server process. Stop it with `Ctrl+C` for now; [Close the connection](#close-the-connection) ends it properly.
## Call a tool
`callTool` takes the tool's name and an `arguments` object that must satisfy its `inputSchema`.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_callTool"
const result = await client.callTool({ name: 'get-alerts', arguments: { state: 'CA' } });
for (const block of result.content) {
if (block.type === 'text') console.log(block.text);
}
```
A tool result is a list of typed **content** blocks; `get-alerts` returns one `text` block. Its text is the live answer from the National Weather Service — one headline per active California alert, or `No active alerts for CA.` when there are none — so your output differs from anyone else's.
A handler that throws, and arguments the `inputSchema` rejects, come back in this same shape with `isError: true` set. A tool name the server never registered is a protocol-level failure, and that one does throw out of `await callTool` — [Errors](../servers/errors.md) draws the line.
::: tip
Change the argument to `{ state: 'California' }` and the SDK rejects it before the handler (and the network request inside it) ever runs:
```text
Input validation error: Invalid arguments for tool get-alerts: state: Too big: expected string to have <=2 characters
```
The rejection is an ordinary `isError: true` result, so a model reads the message and retries with arguments that fit.
:::
## Add a resource and read it
The weather server registers no **resources** yet — a resource is data a client reads by URI, where a tool is an action it invokes. In `src/index.ts`, register one above the `return server` line.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_registerResource"
server.registerResource('about', 'weather://about', { title: 'About this server', mimeType: 'text/plain' }, async uri => ({
contents: [{ uri: uri.href, text: 'Alert data comes from the US National Weather Service.' }]
}));
```
The read handler returns `contents` — a list, because one read can return several text or binary parts. [Resources](../servers/resources.md) covers templates, binary contents, and subscriptions.
Back in `src/client.ts`, list the resources and read the new one by its `uri`.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_readResource"
const { resources } = await client.listResources();
console.log(resources);
const { contents } = await client.readResource({ uri: 'weather://about' });
console.log(contents);
```
Run it again. After the lines you already have, the two new logs are:
```
[
{
name: 'about',
title: 'About this server',
uri: 'weather://about',
mimeType: 'text/plain'
}
]
[
{
uri: 'weather://about',
text: 'Alert data comes from the US National Weather Service.'
}
]
```
`listResources` advertises the metadata you registered; `readResource` returns the handler's `contents` unchanged.
## Close the connection
End the file with `close`.
```ts source="../../examples/guides/get-started/firstClient.examples.ts#firstClient_close"
await client.close();
```
`close()` ends the spawned server's stdin and kills the process if it does not exit on its own. Run the finished script once more: it prints everything above and exits without `Ctrl+C`.
::: tip
In a client that can throw between `connect` and `close`, put `close()` in a `finally` block — otherwise a crash leaves the server process running.
:::
## Hand the tool list to a model
Nothing on this page calls a model. The handoff is `listTools()`: each entry's `name`, `description`, and `inputSchema` — plain JSON Schema — map one-to-one onto the tool definition every tool-calling LLM API takes. Send the conversation with that list; when the model returns a tool call, pass its `name` and `arguments` to `callTool` unchanged and append `result.content` as the tool result.
A host — an application with a model in it — runs that loop for you, through a client of its own. [Plug into a real host](./real-host.md) registers the weather server in VS Code, Claude Code, and Cursor with no client code; `examples/cli-client` in the SDK repository is a complete, provider-neutral host built from the calls on this page.
## Recap
- A `Client` plus one transport is a complete MCP client; `connect()` runs the initialize handshake.
- `StdioClientTransport` spawns and owns the server process — never start it yourself.
- `listTools`, `callTool`, `listResources`, and `readResource` are the client verbs; each returns a typed result.
- A failed handler or rejected arguments come back as an ordinary result with `isError: true` set.
- `close()` tears down the transport and the spawned process.
- A model consumes `listTools()` output unchanged: `name`, `description`, `inputSchema`.

View file

@ -0,0 +1,136 @@
---
status: calibration
shape: tutorial
---
# Build your first server
Build an MCP **server** — a program that exposes tools a model can call — and call its one tool, a US weather-alert lookup, from a client.
## Set up the project
You need Node.js 20 or later and nothing else. Create the project and install the SDK.
```sh
mkdir weather && cd weather
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src
```
`type=module` matters — the SDK ships ES modules only. `tsx` runs TypeScript directly, so there is no build step.
## Register a tool
Create `src/index.ts`: a `createServer` factory that builds an `McpServer` and registers one **tool** — a function the connected model can call.
```ts source="../../examples/guides/get-started/firstServer.examples.ts#firstServer_registerTool"
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const NWS_API = 'https://api.weather.gov';
interface AlertsResponse {
features: { properties: { event?: string; headline?: string } }[];
}
function createServer(): McpServer {
const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.registerTool(
'get-alerts',
{
description: 'Get the active weather alerts for a US state',
inputSchema: z.object({
state: z.string().length(2).describe('Two-letter US state code, e.g. CA')
})
},
async ({ state }) => {
const code = state.toUpperCase();
const url = `${NWS_API}/alerts/active?area=${code}`;
const res = await fetch(url, { headers: { 'User-Agent': 'mcp-weather-tutorial/1.0' } });
if (!res.ok) {
return { content: [{ type: 'text', text: `NWS API error: HTTP ${res.status}` }], isError: true };
}
const { features } = (await res.json()) as AlertsResponse;
if (features.length === 0) {
return { content: [{ type: 'text', text: `No active alerts for ${code}.` }] };
}
const lines = features.map(f => f.properties.headline ?? f.properties.event ?? 'Unnamed alert');
return { content: [{ type: 'text', text: lines.join('\n') }] };
}
);
return server;
}
```
`registerTool` takes a name, a config, and an async handler. `inputSchema` is a Zod schema — the only schema you write. From that one schema the SDK derives the JSON Schema the model sees, validates arguments before your handler runs, and infers the handler's argument types.
The handler returns **content**, a list of typed blocks — one `text` block here. `isError: true` marks a failed result the model can read and react to.
::: tip
Call `get-alerts` with `{ "state": "California" }` and the SDK rejects it before your handler runs. The result is the failure the model sees:
```text
Input validation error: Invalid arguments for tool get-alerts: state: Too big: expected string to have <=2 characters
```
:::
## Serve it over stdio
At the end of the file, hand the factory to `serveStdio`.
```ts source="../../examples/guides/get-started/firstServer.examples.ts#firstServer_serve"
void serveStdio(createServer);
console.error('weather MCP server running on stdio');
```
`serveStdio` owns the **stdio transport**: it reads requests on stdin, writes responses to stdout, and calls `createServer` to build the instance that serves the connection.
::: warning
stdout is the protocol channel. Log with `console.error` — one `console.log` corrupts the JSON-RPC stream.
:::
## Run it
Start the server from the project root.
```sh
npx tsx src/index.ts
```
The banner lands on stderr, leaving stdout for the protocol:
```text
weather MCP server running on stdio
```
Nothing else happens: an stdio server waits on stdin for a client to start the conversation. Stop it with `Ctrl+C`.
## Call the tool
The **MCP Inspector** is a local web app for calling a server's tools directly — it launches the command you give it and connects over stdio.
```sh
npx @modelcontextprotocol/inspector npx tsx src/index.ts
```
In the browser tab it opens, click **Connect**, open the **Tools** tab, select `get-alerts`, enter a two-letter state code such as `TX`, and run it. The text block in the result lists each active alert headline for that state — the same content a model receives when it calls your tool.
## Pick a transport
Your server speaks stdio because a host launches it as a local process and owns its lifetime. To host one endpoint that many clients connect to, serve the same `createServer` factory over [HTTP](../serving/http.md) instead.
Next on this path, [Plug into a real host](./real-host.md) registers this server in VS Code, Claude Code, and Cursor; [Tools](../servers/tools.md) goes deeper on what a tool can return.
## Recap
- `registerTool(name, config, handler)` registers a tool; `inputSchema` is the one Zod schema you write.
- The SDK validates every call against that schema and rejects bad arguments before your handler runs.
- `serveStdio(createServer)` builds the server from your factory and serves it on stdin/stdout.
- stdout carries the protocol; log to stderr.
- `npx @modelcontextprotocol/inspector <command>` exercises any stdio server without a host.

View file

@ -0,0 +1,81 @@
---
shape: explanation
---
# Packages and subpath exports
The SDK is published as nine npm packages. Most projects install exactly one of them.
## Start from one package
Everything in [Build your first server](./first-server.md) came from a single install, `@modelcontextprotocol/server` — through two import paths.
```ts source="../../examples/guides/get-started/packages.examples.ts#packages_serverEntryPoints"
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
```
The first path is the package's root entry. The second is a **subpath export** — a separate entry point inside the same package, declared in its `exports` map.
## Pick the package for your side of the protocol
Install the package for the side of the protocol you are building.
```sh
npm install @modelcontextprotocol/server # expose tools, resources, prompts
npm install @modelcontextprotocol/client # connect to servers and call them
```
Those two are the starting point for almost everything; a process that plays both roles installs both. The full published set is nine packages:
- `@modelcontextprotocol/server` and `@modelcontextprotocol/client` — the two starting points, one per side.
- `@modelcontextprotocol/node`, `@modelcontextprotocol/express`, `@modelcontextprotocol/hono`, `@modelcontextprotocol/fastify` — optional adapters for serving over HTTP.
- `@modelcontextprotocol/core` — the raw Zod wire schemas.
- `@modelcontextprotocol/server-legacy` and `@modelcontextprotocol/codemod` — migration surfaces for v1 code.
A tenth package in the repository, `@modelcontextprotocol/core-internal`, is private: `server` and `client` bundle it at build time, so it never appears in your dependency tree.
## Keep Node-only code behind the `./stdio` subpath
`StdioClientTransport` spawns the server as a child process, so it is exported from `./stdio`, never from the root entry.
```ts source="../../examples/guides/get-started/packages.examples.ts#packages_clientStdioSubpath"
// Runs anywhere: browsers, Workers, Node.
import { Client } from '@modelcontextprotocol/client';
// Spawns a child process — Node-only, so it lives behind the subpath.
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
```
The root entry of every package is **runtime-neutral**: its module graph never reaches `node:child_process` or any other Node builtin a browser or Cloudflare Workers bundler cannot resolve. Importing `./stdio` is the explicit opt-in to a process runtime. `@modelcontextprotocol/server/stdio` is the server-side counterpart and exports `serveStdio` and `StdioServerTransport`.
::: info Coming from v1?
v1's single `@modelcontextprotocol/sdk` package exposed deep file paths such as `@modelcontextprotocol/sdk/server/mcp.js`. The v2 packages declare an `exports` map, so only the subpaths each package names resolve — the codemod rewrites the imports for you.
:::
## Add a framework adapter when you serve over HTTP
`createMcpHandler` from `@modelcontextprotocol/server` already serves MCP over web-standard `Request` and `Response` objects. An adapter package wires that handler into a specific runtime or framework; install the adapter next to the framework it adapts.
```sh
npm install @modelcontextprotocol/express express
```
Four adapters exist: `@modelcontextprotocol/node` for Node's built-in `http` server, and one each for Express, Hono, and Fastify. They are thin layers over `createMcpHandler` and add no MCP behavior of their own.
[Serve over HTTP](../serving/http.md) covers the handler itself; [Express](../serving/express.md), [Hono](../serving/hono.md), and [Fastify](../serving/fastify.md) each have a recipe.
## Reach for `core` only to validate raw wire JSON
`@modelcontextprotocol/core` exports the Zod schema constants the SDK validates protocol payloads against, for code that handles raw JSON-RPC payloads itself — gateways, proxies, log pipelines. Neither `server` nor `client` exports a Zod schema, and the matching TypeScript types ship with both, so if you only call `registerTool` and `callTool` you never install it. [Wire schemas](../advanced/wire-schemas.md) is the how-to.
## Leave `server-legacy` and `codemod` to the migration guide
`@modelcontextprotocol/server-legacy` is a frozen copy of v1's server-side SSE transport and OAuth Authorization Server helpers, published so a v1 deployment can move to v2 without replacing everything at once. `@modelcontextprotocol/codemod` is the command-line tool that rewrites v1 imports and call sites to their v2 forms. Neither belongs in a new project — the [upgrade guide](../migration/upgrade-to-v2.md) covers both.
## Recap
- `@modelcontextprotocol/server` and `@modelcontextprotocol/client` are the two packages you install, one per side of the protocol.
- Package root entries are runtime-neutral; code that spawns processes lives at the `./stdio` subpath and only enters your bundle when you import it.
- The HTTP adapters — `node`, `express`, `hono`, `fastify` — are optional thin layers over `createMcpHandler`; install at most one.
- `@modelcontextprotocol/core` exports only Zod schema constants, for code that validates raw wire JSON itself.
- `@modelcontextprotocol/server-legacy` and `@modelcontextprotocol/codemod` exist for migration from v1, not for new projects.

View file

@ -0,0 +1,136 @@
---
shape: tutorial
---
# Plug into a real host
A **host** is an application with a model in it — VS Code with Copilot, Claude Code, Cursor. Register the weather server from [Build your first server](./first-server.md) in all three and watch the assistant call `get-alerts` on its own.
## Hand the host a launch command
To attach your server, a host needs one thing — a command it can launch as a child process and talk to over that process's stdin and stdout — and `src/index.ts` already ends with the entry that speaks to those two pipes.
```ts source="../../examples/guides/get-started/realHost.examples.ts#realHost_serve"
void serveStdio(createServer);
console.error('weather MCP server running on stdio');
```
So the launch command is the one you already use: `npx tsx src/index.ts`, run from the project root. There is no build step, and there is no new server code on this page — every host below gets that one command.
::: warning
stdout is the protocol channel. One `console.log` and the host drops the connection — log with `console.error`.
:::
## Register the server in VS Code
Create `.vscode/mcp.json` in the `weather` project root, with one stdio entry that holds the launch command.
```json
{
"servers": {
"weather": {
"type": "stdio",
"command": "npx",
"args": ["tsx", "src/index.ts"]
}
}
}
```
VS Code runs the command from the workspace root — the same directory you run it from by hand — and prompts you to trust the new server. Confirm, then run **MCP: List Servers** from the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`): `weather` shows a running status.
::: info
You need VS Code 1.99 or later with the **GitHub Copilot** extension installed and signed in. [Copilot Free](https://github.com/features/copilot/plans) is enough.
:::
## Call the tool from Copilot Chat
Open **Copilot Chat** (`Ctrl+Alt+I` / `Ctrl+Cmd+I`), switch the mode selector at the top of the panel to **Agent** — the only Copilot mode that calls tools — and ask about the one thing your server knows.
```text
What are the active weather alerts in Texas?
```
Copilot stops to show the call it wants to make — `get-alerts` with a two-letter `state` — and waits for you to approve it. Approve, and the handler you wrote in `src/index.ts` runs; the answer in the chat is written from the text block it returned.
Nothing in the prompt names the tool. The model picked `get-alerts` from its name, its description, and the JSON Schema the SDK derived from your `inputSchema`.
::: tip
Click the **Tools** button in the chat panel to see the list the model is choosing from — `get-alerts` is in it, described exactly as you registered it.
:::
## Trace the round trip
That one answer is six steps.
1. VS Code sends your question to the model, along with the name, description, and input schema of every available tool.
2. The model decides `get-alerts` answers the question and emits a call with the arguments it chose.
3. VS Code — the MCP **client** inside the host — sends a `tools/call` request to your server over stdio.
4. The SDK validates the arguments against your `inputSchema` and runs your handler.
5. The handler's `content` goes back over stdout as the `tools/call` result.
6. The model reads the text block and writes the answer you see in the chat.
## Connect other hosts
Every MCP host launches a stdio server from the same command and arguments. Only where you put them differs.
### Claude Code
Register the server from the project root; everything after `--` is the launch command.
```sh
claude mcp add weather -- npx tsx src/index.ts
```
Run `/mcp` inside a Claude Code session in that directory: `weather` is connected, with `get-alerts` listed under it. The same prompt drives the same tool call.
### Cursor
Create `.cursor/mcp.json` in the project root.
```json
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["tsx", "src/index.ts"]
}
}
}
```
The entry is the same `command` plus `args`; only the wrapper key and the file name change. The server appears under Cursor's MCP settings with `get-alerts` listed, and agent chat calls it the same way.
## Fix a host that does not see your tools
Run the launch command by hand, from the project root, before you change any host config.
```sh
npx tsx src/index.ts
```
It prints one line to stderr and then waits.
```text
weather MCP server running on stdio
```
Anything else is the bug, and it has one of three causes.
- The process exits or crashes: the host has nothing to attach to. Fix the command here, where you can read the error, then re-register it.
- Anything besides JSON-RPC reaches stdout: the host reads it as a corrupt message and drops the connection. Find the `console.log` and make it `console.error`.
- The server runs but Copilot never calls it: confirm the chat is in **Agent** mode, then run **MCP: Reset Cached Tools** from the Command Palette.
A command that prints that one line to stderr and waits is one every host on this page can attach to.
## Take the server further
[Tools](../servers/tools.md) covers structured output, annotations, and everything else a handler can return. [HTTP](../serving/http.md) serves the same `createServer` factory as one endpoint many clients share. [Test a server](../testing.md) drives it from an in-memory client — no host, no approval click.
## Recap
- A host launches your server as a child process from a command plus arguments; `npx tsx src/index.ts` is that command, with no build step.
- One `.vscode/mcp.json` entry — `type: stdio`, `command`, `args` — registers the server in VS Code.
- In agent mode the model picks `get-alerts` from its name, description, and input schema; you never name it.
- Claude Code and Cursor take the same launch command; only the config file differs.
- stdout belongs to JSON-RPC — log to stderr or the host drops the connection.