docs(server,examples): document extension capabilities with a runnable example (#2387)

This commit is contained in:
Felix Weinberger 2026-06-29 17:02:22 +01:00 • committed by GitHub
commit 49aeb8eb38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 187 additions and 0 deletions

View file

@ -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 |

View 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
```

View 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();

View 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."
}
}

View 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`);
});
}

View file

@ -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;

View file

@ -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;