docs(server,examples): document extension capabilities with a runnable example (#2387)
This commit is contained in:
parent
f0bf785239
commit
49aeb8eb38
10 changed files with 187 additions and 0 deletions
|
|
@ -166,6 +166,20 @@ const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boole
|
|||
console.log(systemPrompt);
|
||||
```
|
||||
|
||||
### Extension capabilities
|
||||
|
||||
The negotiated server capabilities include `extensions` — a map from extension identifier to that extension's settings object. Read it after connecting via {@linkcode @modelcontextprotocol/client!client/client.Client#getServerCapabilities | client.getServerCapabilities()}:
|
||||
|
||||
```ts source="../examples/guides/clientGuide.examples.ts#extensionCapabilities_read"
|
||||
const extensions = client.getServerCapabilities()?.extensions ?? {};
|
||||
|
||||
if ('com.example/feature-flags' in extensions) {
|
||||
// Advertised on this connection; the entry's value is its settings object.
|
||||
}
|
||||
```
|
||||
|
||||
See [Extension capabilities](./server.md#extension-capabilities) in the server guide for the declaring side.
|
||||
|
||||
## Authentication
|
||||
|
||||
MCP servers can require authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). Pass an {@linkcode @modelcontextprotocol/client!client/auth.AuthProvider |
|
||||
|
|
|
|||
|
|
@ -358,6 +358,22 @@ server.registerPrompt(
|
|||
);
|
||||
```
|
||||
|
||||
## Extension capabilities
|
||||
|
||||
A server advertises support for [MCP extensions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#capability-negotiation) through `capabilities.extensions` — a map from extension identifier to that extension's settings object. Declare entries with
|
||||
{@linkcode @modelcontextprotocol/server!server/server.Server#registerCapabilities | server.server.registerCapabilities()} before connecting:
|
||||
|
||||
```ts source="../examples/guides/serverGuide.examples.ts#extensionCapabilities_register"
|
||||
server.server.registerCapabilities({
|
||||
extensions: { 'com.example/feature-flags': { flags: ['dark-mode', 'beta-search'] } }
|
||||
});
|
||||
```
|
||||
|
||||
The map is advertised in the `initialize` result on legacy connections and in the `server/discover` response on 2026-07-28 ones. Identifiers are prefix-qualified per the spec's `_meta` key naming rules (e.g. `com.example/feature-flags`); each value is free-form JSON for
|
||||
that extension's settings — `{}` means supported with no settings.
|
||||
|
||||
For a runnable pair, see the [`extension-capabilities/` example](../examples/extension-capabilities/README.md); reading the map client-side is covered in the [client guide](./client.md#extension-capabilities).
|
||||
|
||||
## Logging
|
||||
|
||||
> [!WARNING]
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ The one exception to the generic commands is the reference pair: [`cli-client/`]
|
|||
| [`caching/`](./caching/README.md) | `cacheHints` stamping on cacheable results (2026-07-28) | stdio + http | modern |
|
||||
| [`gateway/`](./gateway/README.md) | `connect({ prior })` — probe once, zero-round-trip connect for every worker (gateway pattern) | http | modern |
|
||||
| [`custom-methods/`](./custom-methods/README.md) | Vendor-prefixed methods + custom notifications | stdio + http | dual |
|
||||
| [`extension-capabilities/`](./extension-capabilities/README.md) | Declaring `capabilities.extensions` and reading the negotiated map | stdio + http | dual |
|
||||
| [`schema-validators/`](./schema-validators/README.md) | ArkType, Valibot, Zod, and `outputSchema` | stdio + http | dual |
|
||||
| [`custom-version/`](./custom-version/README.md) | `supportedProtocolVersions` / version negotiation | stdio + http | legacy |
|
||||
| [`parallel-calls/`](./parallel-calls/README.md) | Multiple clients / parallel tool calls, per-client notifications | stdio + http | dual |
|
||||
|
|
|
|||
12
examples/extension-capabilities/README.md
Normal file
12
examples/extension-capabilities/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# extension-capabilities
|
||||
|
||||
The server declares one extension capability, `com.example/feature-flags`, with
|
||||
a small settings object via `server.registerCapabilities({ extensions: { … } })`.
|
||||
The client connects once per era leg and asserts the entry and its settings are
|
||||
advertised — by the `initialize` result on the legacy leg and by
|
||||
`server/discover` on the modern leg.
|
||||
|
||||
```bash
|
||||
pnpm tsx examples/extension-capabilities/client.ts # modern (server/discover)
|
||||
pnpm tsx examples/extension-capabilities/client.ts --legacy # 2025 initialize handshake
|
||||
```
|
||||
36
examples/extension-capabilities/client.ts
Normal file
36
examples/extension-capabilities/client.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Connects to `./server.ts` and asserts the `com.example/feature-flags`
|
||||
* extension capability and its settings are advertised on both the legacy
|
||||
* (`--legacy`, 2025 `initialize`) and modern (`server/discover`) legs.
|
||||
*
|
||||
* Spawns the sibling `server.ts` over stdio by default, or connects to a
|
||||
* running endpoint under `--http <url>`. See `examples/CONTRIBUTING.md` for
|
||||
* the canonical shape.
|
||||
*/
|
||||
import { check, parseExampleArgs, siblingPath } from '@mcp-examples/shared';
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
||||
|
||||
const { transport, url, era } = parseExampleArgs();
|
||||
|
||||
const client = new Client(
|
||||
{ name: 'extension-capabilities-example-client', version: '1.0.0' },
|
||||
{ versionNegotiation: { mode: era === 'modern' ? 'auto' : 'legacy' } }
|
||||
);
|
||||
|
||||
await client.connect(
|
||||
transport === 'stdio'
|
||||
? new StdioClientTransport({ command: 'npx', args: ['-y', 'tsx', siblingPath(import.meta.url, 'server.ts')] })
|
||||
: new StreamableHTTPClientTransport(new URL(url))
|
||||
);
|
||||
|
||||
// Read the negotiated extension map after connecting.
|
||||
const extensions = client.getServerCapabilities()?.extensions ?? {};
|
||||
console.log(
|
||||
`[client] ${era} leg (${client.getNegotiatedProtocolVersion()}) advertised extensions: ${Object.keys(extensions).join(', ') || '(none)'}`
|
||||
);
|
||||
|
||||
check.ok('com.example/feature-flags' in extensions);
|
||||
check.deepEqual(extensions['com.example/feature-flags'], { flags: ['dark-mode', 'beta-search'] });
|
||||
|
||||
await client.close();
|
||||
22
examples/extension-capabilities/package.json
Normal file
22
examples/extension-capabilities/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@mcp-examples/extension-capabilities",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"server": "tsx server.ts",
|
||||
"client": "tsx client.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mcp-examples/shared": "workspace:*",
|
||||
"@modelcontextprotocol/client": "workspace:*",
|
||||
"@modelcontextprotocol/node": "workspace:*",
|
||||
"@modelcontextprotocol/server": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "catalog:devTools"
|
||||
},
|
||||
"example": {
|
||||
"era": "dual",
|
||||
"//": "Both legs assert the extension entry and its settings are advertised."
|
||||
}
|
||||
}
|
||||
37
examples/extension-capabilities/server.ts
Normal file
37
examples/extension-capabilities/server.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Declares one extension capability, `com.example/feature-flags`, with a small
|
||||
* settings object. The entry is advertised to every peer — by the `initialize`
|
||||
* result on legacy connections and by `server/discover` on modern ones.
|
||||
*
|
||||
* One binary, either transport — selected by `--http --port <N>` (defaults to
|
||||
* stdio). See `examples/CONTRIBUTING.md` for the canonical shape.
|
||||
*/
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
import { parseExampleArgs } from '@mcp-examples/shared';
|
||||
import { toNodeHandler } from '@modelcontextprotocol/node';
|
||||
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
|
||||
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
||||
|
||||
function buildServer(): McpServer {
|
||||
const mcp = new McpServer({ name: 'extension-capabilities-server', version: '1.0.0' });
|
||||
|
||||
// Declare the extension and its settings before connecting.
|
||||
mcp.server.registerCapabilities({
|
||||
extensions: { 'com.example/feature-flags': { flags: ['dark-mode', 'beta-search'] } }
|
||||
});
|
||||
|
||||
return mcp;
|
||||
}
|
||||
|
||||
const { transport, port } = parseExampleArgs();
|
||||
|
||||
if (transport === 'stdio') {
|
||||
void serveStdio(buildServer);
|
||||
console.error('[server] serving over stdio');
|
||||
} else {
|
||||
const handler = createMcpHandler(buildServer);
|
||||
createServer(toNodeHandler(handler)).listen(port, () => {
|
||||
console.error(`[server] listening on http://127.0.0.1:${port}/mcp`);
|
||||
});
|
||||
}
|
||||
|
|
@ -142,6 +142,21 @@ async function serverInstructions_basic(client: Client) {
|
|||
//#endregion serverInstructions_basic
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extension capabilities
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Example: Read the negotiated extension capabilities after connecting. */
|
||||
function extensionCapabilities_read(client: Client) {
|
||||
//#region extensionCapabilities_read
|
||||
const extensions = client.getServerCapabilities()?.extensions ?? {};
|
||||
|
||||
if ('com.example/feature-flags' in extensions) {
|
||||
// Advertised on this connection; the entry's value is its settings object.
|
||||
}
|
||||
//#endregion extensionCapabilities_read
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Authentication
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -738,6 +753,7 @@ void connect_sseFallback;
|
|||
void Client_versionNegotiation;
|
||||
void disconnect_streamableHttp;
|
||||
void serverInstructions_basic;
|
||||
void extensionCapabilities_read;
|
||||
void auth_tokenProvider;
|
||||
void auth_clientCredentials;
|
||||
void auth_privateKeyJwt;
|
||||
|
|
|
|||
|
|
@ -276,6 +276,19 @@ function registerPrompt_completion(server: McpServer) {
|
|||
//#endregion registerPrompt_completion
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extension capabilities
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Example: Declare an extension capability with its settings. */
|
||||
function extensionCapabilities_register(server: McpServer) {
|
||||
//#region extensionCapabilities_register
|
||||
server.server.registerCapabilities({
|
||||
extensions: { 'com.example/feature-flags': { flags: ['dark-mode', 'beta-search'] } }
|
||||
});
|
||||
//#endregion extensionCapabilities_register
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Logging
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -599,6 +612,7 @@ void registerResource_static;
|
|||
void registerResource_template;
|
||||
void registerPrompt_basic;
|
||||
void registerPrompt_completion;
|
||||
void extensionCapabilities_register;
|
||||
void streamableHttp_stateful;
|
||||
void streamableHttp_stateless;
|
||||
void streamableHttp_jsonResponse;
|
||||
|
|
|
|||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
|
|
@ -539,6 +539,25 @@ importers:
|
|||
specifier: catalog:devTools
|
||||
version: 4.21.0
|
||||
|
||||
examples/extension-capabilities:
|
||||
dependencies:
|
||||
'@mcp-examples/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@modelcontextprotocol/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/client
|
||||
'@modelcontextprotocol/node':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/middleware/node
|
||||
'@modelcontextprotocol/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/server
|
||||
devDependencies:
|
||||
tsx:
|
||||
specifier: catalog:devTools
|
||||
version: 4.21.0
|
||||
|
||||
examples/gateway:
|
||||
dependencies:
|
||||
'@mcp-examples/shared':
|
||||
|
|
|
|||
Loading…
Reference in a new issue