headersFor(['method' => 'tools/list', 'params' => []]); $this->assertSame('2026-07-28', $headers['MCP-Protocol-Version']); $this->assertSame('tools/list', $headers['Mcp-Method']); $this->assertArrayNotHasKey('Mcp-Name', $headers); } #[TestDox('names the subject of a request that addresses one')] public function testNameHeader(): void { $payload = ['method' => 'tools/call', 'params' => ['name' => 'search', 'arguments' => []]]; $this->assertSame('search', $this->headersFor($payload)['Mcp-Name']); $this->assertNull($this->validate($payload)); } #[TestDox('wraps a subject that is not header-safe, and the server unwraps it')] public function testUnsafeNameRoundTrips(): void { $payload = ['method' => 'resources/read', 'params' => ['uri' => 'file:///café.txt']]; $this->assertSame('=?base64?ZmlsZTovLy9jYWbDqS50eHQ=?=', $this->headersFor($payload)['Mcp-Name']); $this->assertNull($this->validate($payload)); } #[TestDox('mirrors annotated tool arguments the server can verify')] public function testMirroredArguments(): void { $payload = [ 'method' => 'tools/call', 'params' => [ 'name' => 'search', 'arguments' => ['region' => ' padded ', 'priority' => 7], ], ]; $headers = $this->headersFor($payload); $this->assertSame('=?base64?IHBhZGRlZCA=?=', $headers['Mcp-Param-Region']); $this->assertSame('7', $headers['Mcp-Param-Priority']); $this->assertNull($this->validate($payload)); } #[TestDox('a response carries the revision but nothing to mirror')] public function testResponseCarriesOnlyTheVersion(): void { $headers = $this->headersFor(['id' => 1, 'result' => []]); $this->assertSame(['MCP-Protocol-Version' => '2026-07-28'], $headers); } /** * @param array $payload * * @return array */ private function headersFor(array $payload): array { return (new HeaderFactory($this->catalog()))->forMessage($payload, ProtocolVersion::V2026_07_28); } /** * @param array $payload * * @return string|null the server's reason to reject, or null when it agrees */ private function validate(array $payload): ?string { return (new StandardHeaderValidator())->validate( $payload['method'], $payload['params'] ?? null, $this->headersFor($payload), ); } private function catalog(): ToolCatalog { $catalog = new ToolCatalog(); $catalog->record([[ 'name' => 'search', 'inputSchema' => [ 'type' => 'object', 'properties' => [ 'region' => ['type' => 'string', 'x-mcp-header' => 'Region'], 'priority' => ['type' => 'integer', 'x-mcp-header' => 'Priority'], ], ], ]]); return $catalog; } }