feat(core): reserved trace context _meta keys and propagation docs (SEP-414) (#2270)
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
parent
e84c3e9ad0
commit
78fbe2736d
2 changed files with 330 additions and 5 deletions
|
|
@ -21,7 +21,9 @@ import {
|
|||
SdkError,
|
||||
SdkErrorCode,
|
||||
SSEClientTransport,
|
||||
StreamableHTTPClientTransport
|
||||
StreamableHTTPClientTransport,
|
||||
TRACEPARENT_META_KEY,
|
||||
TRACESTATE_META_KEY
|
||||
} from '@modelcontextprotocol/client';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
||||
//#endregion imports
|
||||
|
|
@ -522,6 +524,55 @@ async function middleware_basic() {
|
|||
return transport;
|
||||
}
|
||||
|
||||
/** Example: Attach W3C Trace Context to a single request via `_meta`. */
|
||||
async function traceContext_perRequest(client: Client) {
|
||||
//#region traceContext_perRequest
|
||||
// Values would normally come from your tracer's active span context.
|
||||
const result = await client.callTool({
|
||||
name: 'calculate-bmi',
|
||||
arguments: { weightKg: 70, heightM: 1.75 },
|
||||
_meta: {
|
||||
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
||||
[TRACESTATE_META_KEY]: 'vendor1=opaqueValue1'
|
||||
}
|
||||
});
|
||||
console.log(result.content);
|
||||
//#endregion traceContext_perRequest
|
||||
}
|
||||
|
||||
/** Example: Client middleware that injects trace context into every outgoing request. */
|
||||
async function traceContext_middleware() {
|
||||
//#region traceContext_middleware
|
||||
const traceContextMiddleware = createMiddleware(async (next, input, init) => {
|
||||
if (typeof init?.body !== 'string') {
|
||||
return next(input, init);
|
||||
}
|
||||
const message = JSON.parse(init.body) as {
|
||||
method?: string;
|
||||
params?: { _meta?: Record<string, unknown>; [key: string]: unknown };
|
||||
};
|
||||
// Only requests and notifications carry params._meta; skip responses.
|
||||
if (message.method === undefined) {
|
||||
return next(input, init);
|
||||
}
|
||||
message.params = {
|
||||
...message.params,
|
||||
_meta: {
|
||||
...message.params?._meta,
|
||||
// Replace with values from your tracer's active span context.
|
||||
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
|
||||
}
|
||||
};
|
||||
return next(input, { ...init, body: JSON.stringify(message) });
|
||||
});
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
|
||||
fetch: applyMiddlewares(traceContextMiddleware)(fetch)
|
||||
});
|
||||
//#endregion traceContext_middleware
|
||||
return transport;
|
||||
}
|
||||
|
||||
/** Example: Track resumption tokens for SSE reconnection. */
|
||||
async function resumptionToken_basic(client: Client) {
|
||||
//#region resumptionToken_basic
|
||||
|
|
@ -572,4 +623,6 @@ void errorHandling_toolErrors;
|
|||
void errorHandling_lifecycle;
|
||||
void errorHandling_timeout;
|
||||
void middleware_basic;
|
||||
void traceContext_perRequest;
|
||||
void traceContext_middleware;
|
||||
void resumptionToken_basic;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { randomUUID } from 'node:crypto';
|
|||
import { createMcpExpressApp } from '@modelcontextprotocol/express';
|
||||
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
||||
import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server';
|
||||
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
|
||||
import { completable, McpServer, ResourceTemplate, TRACEPARENT_META_KEY } from '@modelcontextprotocol/server';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
|
||||
import * as z from 'zod/v4';
|
||||
//#endregion imports
|
||||
|
|
@ -319,6 +319,29 @@ function registerTool_progress(server: McpServer) {
|
|||
//#endregion registerTool_progress
|
||||
}
|
||||
|
||||
/** Example: Tool that reads W3C Trace Context from request `_meta`. */
|
||||
function registerTool_traceContext(server: McpServer) {
|
||||
//#region registerTool_traceContext
|
||||
server.registerTool(
|
||||
'traced-operation',
|
||||
{
|
||||
description: 'Operation that participates in distributed tracing',
|
||||
inputSchema: z.object({ query: z.string() })
|
||||
},
|
||||
async ({ query }, ctx): Promise<CallToolResult> => {
|
||||
// e.g. '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
|
||||
const traceparent = ctx.mcpReq._meta?.[TRACEPARENT_META_KEY];
|
||||
if (typeof traceparent === 'string') {
|
||||
// Continue the caller's trace, e.g. start a child span with your
|
||||
// OpenTelemetry tracer using this trace context.
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: `Results for ${query}` }] };
|
||||
}
|
||||
);
|
||||
//#endregion registerTool_traceContext
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server-initiated requests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -543,6 +566,7 @@ void registerTool_errorHandling;
|
|||
void registerTool_annotations;
|
||||
void registerTool_logging;
|
||||
void registerTool_progress;
|
||||
void registerTool_traceContext;
|
||||
void registerTool_sampling;
|
||||
void registerTool_elicitation;
|
||||
void registerTool_roots;
|
||||
|
|
|
|||
Loading…
Reference in a new issue