Skip to content

Commit 757e7d3

Browse files
authored
Merge pull request #312929 from zhichli/zhichli/mcp-traceparent-sep414
MCP: forward W3C traceparent to servers via _meta (SEP-414)
2 parents a7bcda7 + a106aa6 commit 757e7d3

10 files changed

Lines changed: 103 additions & 3 deletions

File tree

extensions/copilot/src/extension/tools/vscode-node/toolsService.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ export class ToolsService extends BaseToolsService {
175175

176176
const startTime = Date.now();
177177

178+
// Propagate W3C trace context to tool invocations so downstream spans can be
179+
// correlated with this `execute_tool` span. MCP tools forward this onto
180+
// `_meta.traceparent`/`_meta.tracestate` of the JSON-RPC `tools/call` payload
181+
// (MCP SEP-414, see #302301). Only set if not already supplied by the caller.
182+
const optionsWithTrace = options as vscode.LanguageModelToolInvocationOptions<Object> & { traceparent?: string; tracestate?: string };
183+
const ctx = span.getSpanContext();
184+
if (ctx) {
185+
if (!optionsWithTrace.traceparent) {
186+
// Preserve the upstream W3C trace flags when available. Fall back to `01`
187+
// (sampled) so downstream MCP servers continue to participate in the trace
188+
// when the abstraction does not surface flags (e.g. tests, in-memory impl).
189+
const flags = (ctx.traceFlags ?? 0x01).toString(16).padStart(2, '0');
190+
optionsWithTrace.traceparent = `00-${ctx.traceId}-${ctx.spanId}-${flags}`;
191+
}
192+
if (!optionsWithTrace.tracestate && ctx.traceState) {
193+
optionsWithTrace.tracestate = ctx.traceState;
194+
}
195+
}
196+
178197
return vscode.lm.invokeTool(getContributedToolName(name), options, token).then(
179198
result => {
180199
span.setStatus(SpanStatusCode.OK);

extensions/copilot/src/platform/otel/common/otelService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ export const IOTelService = createServiceIdentifier<IOTelService>('IOTelService'
1515
export interface TraceContext {
1616
readonly traceId: string;
1717
readonly spanId: string;
18+
/**
19+
* W3C trace flags from the source span context (e.g. `0x01` for sampled). Optional
20+
* because not all impls preserve it; consumers that build a W3C `traceparent` should
21+
* fall back to a sampled value when unset.
22+
*/
23+
readonly traceFlags?: number;
24+
/** W3C tracestate serialized as a comma-separated key=value list, when present. */
25+
readonly traceState?: string;
1826
}
1927

2028
/**

extensions/copilot/src/platform/otel/node/otelServiceImpl.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ export class NodeOTelService implements IOTelService {
344344
if (!ctx.traceId || !ctx.spanId) {
345345
return undefined;
346346
}
347-
return { traceId: ctx.traceId, spanId: ctx.spanId };
347+
return { traceId: ctx.traceId, spanId: ctx.spanId, traceFlags: ctx.traceFlags, traceState: ctx.traceState?.serialize() };
348348
}
349349

350350
// ── Trace Context Store ── (for cross-boundary propagation)
@@ -620,7 +620,9 @@ class RealSpanHandle implements ISpanHandle {
620620

621621
getSpanContext(): TraceContext | undefined {
622622
const ctx = this._span.spanContext();
623-
return ctx.traceId && ctx.spanId ? { traceId: ctx.traceId, spanId: ctx.spanId } : undefined;
623+
return ctx.traceId && ctx.spanId
624+
? { traceId: ctx.traceId, spanId: ctx.spanId, traceFlags: ctx.traceFlags, traceState: ctx.traceState?.serialize() }
625+
: undefined;
624626
}
625627

626628
end(): void {

src/vs/workbench/api/common/extHostLanguageModelTools.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
130130
subAgentInvocationId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.subAgentInvocationId : undefined,
131131
chatStreamToolCallId: isProposedApiEnabled(extension, 'chatParticipantAdditions') ? options.chatStreamToolCallId : undefined,
132132
preToolUseResult: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.preToolUseResult : undefined,
133+
traceparent: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.traceparent : undefined,
134+
tracestate: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.tracestate : undefined,
133135
}, token);
134136

135137
const dto: Dto<IToolResult> = result instanceof SerializableObjectWithBuffers ? result.value : result;
@@ -191,6 +193,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
191193
options.chatInteractionId = dto.chatInteractionId;
192194
options.chatSessionResource = URI.revive(dto.context?.sessionResource);
193195
options.subAgentInvocationId = dto.subAgentInvocationId;
196+
options.traceparent = dto.traceparent;
197+
options.tracestate = dto.tracestate;
194198
}
195199

196200
if (isProposedApiEnabled(item.extension, 'chatParticipantAdditions') && dto.modelId) {

src/vs/workbench/contrib/chat/common/tools/languageModelToolsService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ export interface IToolInvocation {
197197
selectedCustomButton?: string;
198198
/** Pre-tool-use hook result passed from the extension, if the hook was already executed externally. */
199199
preToolUseResult?: IExternalPreToolUseHookResult;
200+
/**
201+
* Optional W3C trace context `traceparent` value identifying the parent distributed
202+
* tracing span for this tool invocation. Forwarded to MCP tool implementations as
203+
* `_meta.traceparent` (MCP SEP-414).
204+
*/
205+
traceparent?: string;
206+
/** Optional W3C trace context `tracestate` value paired with {@link traceparent}. */
207+
tracestate?: string;
200208
}
201209

202210
export interface IToolInvocationContext {

src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,12 @@ class McpToolImplementation implements IToolImpl {
266266
content: []
267267
};
268268

269-
const callResult = await this._tool.callWithProgress(invocation.parameters as Record<string, unknown>, progress, { chatRequestId: invocation.chatRequestId, chatSessionResource: invocation.context?.sessionResource }, token);
269+
const callResult = await this._tool.callWithProgress(invocation.parameters as Record<string, unknown>, progress, {
270+
chatRequestId: invocation.chatRequestId,
271+
chatSessionResource: invocation.context?.sessionResource,
272+
traceparent: invocation.traceparent,
273+
tracestate: invocation.tracestate,
274+
}, token);
270275
const details: Mutable<IToolResultInputOutputDetails> = {
271276
input: JSON.stringify(invocation.parameters, undefined, 2),
272277
output: [],

src/vs/workbench/contrib/mcp/common/mcpServer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,14 @@ export class McpTool implements IMcpTool {
11991199
if (context?.chatRequestId) {
12001200
meta['vscode.requestId'] = context.chatRequestId;
12011201
}
1202+
// Propagate W3C trace context to the MCP server (MCP SEP-414) so server-side
1203+
// spans can be correlated with the client trace.
1204+
if (context?.traceparent) {
1205+
meta['traceparent'] = context.traceparent;
1206+
if (context.tracestate) {
1207+
meta['tracestate'] = context.tracestate;
1208+
}
1209+
}
12021210

12031211
const taskHint = this._definition.execution?.taskSupport;
12041212
const serverSupportsTasksForTools = h.capabilities.tasks?.requests?.tools?.call !== undefined;

src/vs/workbench/contrib/mcp/common/mcpTypes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,13 @@ export interface IMcpPromptMessage extends MCP.PromptMessage { }
453453
export interface IMcpToolCallContext {
454454
chatSessionResource: URI | undefined;
455455
chatRequestId?: string;
456+
/**
457+
* Optional W3C trace context `traceparent` value to forward to the MCP server
458+
* via `_meta.traceparent` on the JSON-RPC `tools/call` request (MCP SEP-414).
459+
*/
460+
traceparent?: string;
461+
/** Optional W3C trace context `tracestate` value paired with {@link traceparent}. */
462+
tracestate?: string;
456463
}
457464

458465
/**

src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,34 @@ suite('Workbench - MCP - ServerRequestHandler', () => {
381381
assert.strictEqual(e.name, 'Canceled');
382382
}
383383
});
384+
385+
test('callTool forwards _meta.traceparent to the JSON-RPC payload (MCP SEP-414)', async () => {
386+
const traceparent = '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01';
387+
const tracestate = 'rojo=00f067aa0ba902b7';
388+
389+
const callPromise = handler.callTool({
390+
name: 'echo',
391+
arguments: { hello: 'world' },
392+
_meta: { traceparent, tracestate, progressToken: 'tok-1' },
393+
});
394+
395+
const sentMessages = transport.getSentMessages();
396+
const callRequest = sentMessages[2] as MCP.JSONRPCRequest & MCP.CallToolRequest;
397+
assert.strictEqual(callRequest.method, 'tools/call');
398+
assert.deepStrictEqual(callRequest.params._meta, {
399+
traceparent,
400+
tracestate,
401+
progressToken: 'tok-1',
402+
});
403+
404+
transport.simulateReceiveMessage({
405+
jsonrpc: MCP.JSONRPC_VERSION,
406+
id: callRequest.id,
407+
result: { content: [] },
408+
});
409+
410+
await callPromise;
411+
});
384412
});
385413

386414
suite.skip('Workbench - MCP - McpTask', () => { // TODO@connor4312 https://github.com/microsoft/vscode/issues/280126

src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ declare module 'vscode' {
303303
* Unique ID for the subagent invocation, used to group tool calls from the same subagent run together.
304304
*/
305305
subAgentInvocationId?: string;
306+
/**
307+
* W3C trace context `traceparent` header value identifying the active distributed
308+
* tracing span. When provided to a tool implementation backed by an MCP server, this
309+
* value is forwarded as `_meta.traceparent` on the JSON-RPC `tools/call` request so
310+
* downstream servers can correlate their spans (MCP SEP-414).
311+
*/
312+
traceparent?: string;
313+
/**
314+
* Optional W3C trace context `tracestate` header value paired with `traceparent`.
315+
*/
316+
tracestate?: string;
306317
/**
307318
* Pre-tool-use hook result, if the hook was already executed by the caller.
308319
* When provided, the tools service will skip executing its own preToolUse hook

0 commit comments

Comments
 (0)