[Server] Carry trace context through a request (#452)

traceparent, tracestate and baggage reach handlers through RequestContext
and are echoed onto the notifications the request causes, so a span is not
broken by the response stream. Passed through verbatim: what they mean is
the tracing library's business, and no dependency is taken for it.
This commit is contained in:
Christopher Hertel 2026-08-19 00:20:32 +02:00 • committed by GitHub
commit 855011b8dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -122,6 +122,18 @@ class StatelessProtocolTest extends TestCase
name: 'asks_by_url_after_progress',
description: 'Emits progress, then asks through a url-mode elicitation',
)
->addTool(
static function (RequestContext $context): string {
$pairs = [];
foreach ($context->getTraceContext() as $key => $value) {
$pairs[] = $key.'='.$value;
}
return implode(';', $pairs) ?: 'none';
},
name: 'probe_trace',
description: 'Reports the trace context it was called with',
)
->addResource(static fn (): string => 'body', 'test://static', 'static', 'A static resource')
->addResource(
static function (RequestContext $context): string|InputRequiredResult {
@ -150,9 +162,11 @@ class StatelessProtocolTest extends TestCase
*/
private static function call(StatelessProtocol $protocol, string $method, array $params = [], array $extraHeaders = [], array $capabilities = []): array
{
// Merged, not replaced: a test may add its own `_meta` members.
$params['_meta'] = [
RequestMeta::PROTOCOL_VERSION => ProtocolVersion::V2026_07_28->value,
RequestMeta::CLIENT_CAPABILITIES => (object) $capabilities,
...($params['_meta'] ?? []),
];
return self::callWithHeaders($protocol, $method, $params, [
@ -409,6 +423,55 @@ class StatelessProtocolTest extends TestCase
$this->assertStringContainsString('InputRequiredResult', $body['error']['message']);
}
#[TestDox('a request\'s trace context reaches the handler')]
public function testTraceContextReachesTheHandler(): void
{
$answer = self::call(
self::protocol(),
'tools/call',
[
'name' => 'probe_trace',
'arguments' => [],
'_meta' => [
RequestMeta::PROTOCOL_VERSION => ProtocolVersion::V2026_07_28->value,
RequestMeta::CLIENT_CAPABILITIES => new \stdClass(),
'traceparent' => '00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01',
'baggage' => 'tenant=acme',
],
],
['Mcp-Name' => 'probe_trace'],
);
$this->assertSame(
'traceparent=00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01;baggage=tenant=acme',
$answer['body']['result']['content'][0]['text'],
);
}
#[TestDox('notifications caused by a traced request carry its trace context')]
public function testNotificationsCarryTheTraceContext(): void
{
$result = self::callStreaming(self::protocol(), 'progress_tool', [
'progressToken' => 'tok-1',
'traceparent' => '00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01',
]);
$frames = self::frames($result);
$this->assertSame(
'00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01',
$frames[0]['params']['_meta']['traceparent'],
);
}
#[TestDox('an untraced request adds no trace metadata')]
public function testUntracedRequestAddsNothing(): void
{
$frames = self::frames(self::callStreaming(self::protocol(), 'progress_tool', ['progressToken' => 'tok-1']));
$this->assertArrayNotHasKey('traceparent', $frames[0]['params']['_meta'] ?? []);
}
#[TestDox('a missing resource answers -32602 with the uri, not the retired -32002')]
public function testResourceNotFoundUsesInvalidParams(): void
{