Address valid review comments

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2026-05-18 17:12:19 +02:00
parent 00bdd871a3
commit 0422010a37
5 changed files with 65 additions and 10 deletions
@@ -1055,6 +1055,37 @@ describe('McpService', () => {
expect(options?.attributes).not.toHaveProperty('gen_ai.agent.id');
});
it('threads baggage end-to-end from a propagated baggage header through context.with into the tool span', async () => {
const tracing = tracingServiceMock.mock();
// Simulate what the routers do on incoming requests: extract context
// from headers and run the handler with that context active.
const ctx = tracing.propagation.extract(tracing.context.active(), {
baggage: 'gen_ai.conversation.id=conv-end-to-end',
});
await tracing.context.with(ctx, () => invokeMockAction({ tracing }));
const [, options] = tracing.startActiveSpan.mock.calls[0];
expect(options?.attributes?.['gen_ai.conversation.id']).toBe(
'conv-end-to-end',
);
});
it('truncates overlong baggage values before stamping them on the span', async () => {
const tracing = tracingServiceMock.mock();
const longValue = 'a'.repeat(1024);
tracing.propagation.getActiveBaggage.mockReturnValue({
getAllEntries: () => [['gen_ai.conversation.id', { value: longValue }]],
});
await invokeMockAction({ tracing });
const [, options] = tracing.startActiveSpan.mock.calls[0];
const recorded = options?.attributes?.['gen_ai.conversation.id'];
expect(typeof recorded).toBe('string');
expect((recorded as string).length).toBe(256);
expect(recorded).toBe('a'.repeat(256));
});
it('includes tool arguments in the span options and sets the structured action output as the result attribute when captureToolPayloads is true', async () => {
const tracing = tracingServiceMock.mock();
await invokeMockAction({ tracing, captureToolPayloads: true });
@@ -54,6 +54,12 @@ const PROPAGATED_BAGGAGE_ATTRIBUTES: ReadonlySet<string> = new Set([
'gen_ai.request.model',
]);
// Cap each forwarded baggage value before it lands on a span attribute.
// Baggage values are caller-controlled strings of unbounded length;
// allowlisting keys protects against arbitrary attribute names but not
// against pathologically large values inflating exported span sizes.
const BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH = 256;
function baggageAttributes(
tracingService: TracingService,
): Record<string, string> {
@@ -62,7 +68,7 @@ function baggageAttributes(
const attrs: Record<string, string> = {};
for (const [key, entry] of baggage.getAllEntries()) {
if (PROPAGATED_BAGGAGE_ATTRIBUTES.has(key)) {
attrs[key] = entry.value;
attrs[key] = entry.value.slice(0, BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH);
}
}
return attrs;
@@ -219,6 +225,11 @@ export class McpService {
}
// Re-attribute the span to the plugin that owns the action.
// This runs after the span has started, so head-based samplers
// still see the default `mcp-actions` value when deciding
// whether to record the span. The pluginId is only known after
// resolving the action via `actions.list`, so the reattribution
// is unavoidable.
span.setAttribute('backstage.plugin.id', action.pluginId);
const { output } = await this.actions.invoke({