docs: rebuild the v2 documentation around a task-based page tree (#2397)
This commit is contained in:
parent
708d5455d9
commit
b153505428
1 changed files with 11884 additions and 4971 deletions
159
docs/troubleshooting.md
Normal file
159
docs/troubleshooting.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
shape: reference
|
||||
---
|
||||
# Troubleshooting
|
||||
|
||||
Each heading on this page is the verbatim error message. Match yours, then apply that entry's fix.
|
||||
|
||||
## `SyntaxError: Unexpected token ... is not valid JSON`
|
||||
|
||||
On stdio, standard output is the wire: the host parses every line your server writes to `stdout` as JSON-RPC. One `console.log` — yours or a dependency's — puts a stray line on it, and the host reports that line with this error. Log to `stderr` instead; `serveStdio` owns `stdout`, and `console.error` is safe anywhere in the process.
|
||||
|
||||
```ts source="../examples/guides/troubleshooting.stdio.examples.ts#serveStdio_stderr"
|
||||
import { McpServer } from '@modelcontextprotocol/server';
|
||||
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
||||
|
||||
serveStdio(() => {
|
||||
const server = new McpServer({ name: 'app', version: '1.0.0' });
|
||||
console.error('app server running on stdio'); // stderr — never console.log
|
||||
return server;
|
||||
});
|
||||
```
|
||||
|
||||
The host shows the `stderr` line in the server's log and keeps parsing `stdout` cleanly. [Serve over stdio](./serving/stdio.md) covers the entry point.
|
||||
|
||||
::: tip
|
||||
The quoted token is the first character of the stray line, which usually identifies the call that wrote it.
|
||||
:::
|
||||
|
||||
## `TS2589: Type instantiation is excessively deep and possibly infinite`
|
||||
|
||||
Two copies of `zod` in the dependency tree. The SDK derives its tool, prompt and resource types from Zod v4 schemas; a second `zod` copy makes TypeScript instantiate cross-version types until it hits its recursion limit and fails at an unrelated-looking call site.
|
||||
|
||||
List every installed copy:
|
||||
|
||||
```sh
|
||||
npm ls zod # or: pnpm why zod / yarn why zod
|
||||
```
|
||||
|
||||
Align everything on one Zod 4 version. When a transitive dependency pins another copy, force one with your package manager's override field (`overrides` for npm and pnpm, `resolutions` for Yarn):
|
||||
|
||||
```json
|
||||
{
|
||||
"overrides": {
|
||||
"zod": "^4.2.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`npm ls zod` reporting a single version means the duplicate is gone and the error with it.
|
||||
|
||||
## `ReferenceError: crypto is not defined`
|
||||
|
||||
The OAuth client helpers sign and verify through the Web Crypto API at `globalThis.crypto`. Every `@modelcontextprotocol/*` package requires Node.js 20, where that global is always defined — this error means the process is running on an older runtime (Node.js 18 and earlier).
|
||||
|
||||
Upgrade Node.js. Where you cannot, assign the polyfill from `node:crypto` before anything touches the SDK:
|
||||
|
||||
```ts source="../examples/guides/troubleshooting.examples.ts#webcrypto_polyfill"
|
||||
import { webcrypto } from 'node:crypto';
|
||||
|
||||
if (typeof globalThis.crypto === 'undefined') {
|
||||
globalThis.crypto = webcrypto;
|
||||
}
|
||||
```
|
||||
|
||||
With the global in place the [client OAuth](./clients/oauth.md) flows run unchanged.
|
||||
|
||||
## `SdkError: ERA_NEGOTIATION_FAILED`
|
||||
|
||||
`connect()` found no **protocol era** both sides speak. Two shapes produce it: `versionNegotiation: { mode: { pin: ... } }` names a revision the server does not offer over `server/discover`, and pinning never falls back; or `mode: 'auto'` with a `supportedProtocolVersions` list that has no pre-2026 entry, which removes the legacy fallback.
|
||||
|
||||
The pinned shape — `transport` here reaches a server still on the 2025 revisions ([Test a server](./testing.md) shows the in-memory wiring these outputs come from):
|
||||
|
||||
```ts source="../examples/guides/troubleshooting.examples.ts#connect_pinRejected"
|
||||
const pinned = new Client({ name: 'app', version: '1.0.0' }, { versionNegotiation: { mode: { pin: '2026-07-28' } } });
|
||||
|
||||
try {
|
||||
await pinned.connect(transport);
|
||||
} catch (error) {
|
||||
if (!(error instanceof SdkError)) throw error;
|
||||
console.log(`${error.code}: ${error.message}`);
|
||||
}
|
||||
```
|
||||
|
||||
The rejection names the pinned revision the server never offered:
|
||||
|
||||
```
|
||||
ERA_NEGOTIATION_FAILED: Version negotiation failed: the server did not offer pinned protocol version 2026-07-28 via server/discover (no fallback in pin mode)
|
||||
```
|
||||
|
||||
Change the mode to `'auto'`: the probe falls back to the 2025 `initialize` handshake on the same connection.
|
||||
|
||||
```ts source="../examples/guides/troubleshooting.examples.ts#connect_autoFallback"
|
||||
const negotiated = new Client({ name: 'app', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
|
||||
|
||||
await negotiated.connect(transport);
|
||||
console.log(negotiated.getProtocolEra());
|
||||
```
|
||||
|
||||
`connect()` resolves and the client reports the era it landed on:
|
||||
|
||||
```
|
||||
legacy
|
||||
```
|
||||
|
||||
Keep `{ pin }` where a legacy connection is unacceptable and a hard failure is the behavior you want. [Protocol versions](./protocol-versions.md) defines the eras and what each negotiation mode offers.
|
||||
|
||||
## `SdkError: METHOD_NOT_SUPPORTED_BY_PROTOCOL_VERSION`
|
||||
|
||||
You sent a spec method the negotiated protocol era does not define. The SDK raises this locally — nothing reached the transport — and the message names the method, the negotiated revision, and the era-appropriate replacement.
|
||||
|
||||
`subscriptions/listen` exists only on a 2026-07-28 connection; this client negotiated a 2025 era, the default:
|
||||
|
||||
```ts source="../examples/guides/troubleshooting.examples.ts#listen_legacyConnection"
|
||||
const client = new Client({ name: 'app', version: '1.0.0' });
|
||||
await client.connect(transport);
|
||||
|
||||
try {
|
||||
await client.listen({ resourceSubscriptions: ['file:///logs/app.log'] });
|
||||
} catch (error) {
|
||||
if (!(error instanceof SdkError)) throw error;
|
||||
console.log(`${error.code}: ${error.message}`);
|
||||
}
|
||||
```
|
||||
|
||||
The message carries the fix:
|
||||
|
||||
```
|
||||
METHOD_NOT_SUPPORTED_BY_PROTOCOL_VERSION: subscriptions/listen requires a 2026-07-28-era connection (negotiated: 2025-11-25). On a 2025-era connection, change notifications are delivered unsolicited: use ClientOptions.listChanged and resources/subscribe instead.
|
||||
```
|
||||
|
||||
Either negotiate the era that defines the method — `versionNegotiation: { mode: 'auto' }` against a server that serves 2026-07-28, as in the previous entry — or call the surface the negotiated era does define. [Subscriptions](./clients/subscriptions.md) covers both delivery models; [Protocol versions](./protocol-versions.md) lists which methods each era defines.
|
||||
|
||||
## `Module '"@modelcontextprotocol/server"' has no exported member 'SSEServerTransport'`
|
||||
|
||||
`@modelcontextprotocol/server` no longer ships the server-side SSE transport, and the OAuth Authorization Server helpers (`mcpAuthRouter`, `ProxyOAuthServerProvider`) left with it. Both live on as a frozen v1 copy in `@modelcontextprotocol/server-legacy`.
|
||||
|
||||
Rewrite the imports:
|
||||
|
||||
```diff
|
||||
- import { SSEServerTransport } from '@modelcontextprotocol/server';
|
||||
+ import { SSEServerTransport } from '@modelcontextprotocol/server-legacy/sse';
|
||||
|
||||
- import { mcpAuthRouter, ProxyOAuthServerProvider } from '@modelcontextprotocol/server';
|
||||
+ import { mcpAuthRouter, ProxyOAuthServerProvider } from '@modelcontextprotocol/server-legacy/auth';
|
||||
```
|
||||
|
||||
The Resource Server helpers did not move there: `requireBearerAuth`, `mcpAuthMetadataRouter` and `OAuthTokenVerifier` are first-class in `@modelcontextprotocol/express` — see [Authorization](./serving/authorization.md). `@modelcontextprotocol/server-legacy` is frozen and receives no new features; serve new code over [Streamable HTTP](./serving/http.md), which still reaches 2025-era clients through [legacy client support](./serving/legacy-clients.md). A client limited to the HTTP+SSE transport is the one case that still needs the frozen `@modelcontextprotocol/server-legacy/sse` import above.
|
||||
|
||||
## Recap
|
||||
|
||||
- Every heading on this page is the exact message you searched for.
|
||||
- On stdio, `stdout` carries JSON-RPC; log with `console.error`.
|
||||
- `TS2589` means two `zod` copies in the dependency tree.
|
||||
- The SDK raises `ERA_NEGOTIATION_FAILED` and `METHOD_NOT_SUPPORTED_BY_PROTOCOL_VERSION` locally — neither is a wire error.
|
||||
- Server SSE and the Authorization Server helpers live in `@modelcontextprotocol/server-legacy`.
|
||||
|
||||
<!-- maintainers: an entry on this page lives only as long as the surface that
|
||||
produces it. When an era, package, or supported Node.js version is removed,
|
||||
delete its entries in the same change — this page never accretes. -->
|
||||
Loading…
Reference in a new issue