docs: rebuild the v2 documentation around a task-based page tree (#2397)
This commit is contained in:
parent
708d5455d9
commit
b153505428
5 changed files with 11884 additions and 4971 deletions
9
docs/get-started/examples.md
Normal file
9
docs/get-started/examples.md
Normal 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.
|
||||
157
docs/get-started/first-client.md
Normal file
157
docs/get-started/first-client.md
Normal 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`.
|
||||
136
docs/get-started/first-server.md
Normal file
136
docs/get-started/first-server.md
Normal 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.
|
||||
81
docs/get-started/packages.md
Normal file
81
docs/get-started/packages.md
Normal 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.
|
||||
136
docs/get-started/real-host.md
Normal file
136
docs/get-started/real-host.md
Normal 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.
|
||||
Loading…
Reference in a new issue