*/ final class HeaderFactory { public function __construct( private readonly ToolCatalog $tools, ) { } /** * @param array $payload a serialized JSON-RPC message * * @return array */ public function forMessage(array $payload, ProtocolVersion $protocolVersion): array { $method = $payload['method'] ?? null; $params = \is_array($payload['params'] ?? null) ? $payload['params'] : null; // A response to a server-initiated request carries no method to mirror; // the version header is unconditional and still applies. Trace context // still rides along if the application put it in `_meta` — the party // that started the trace is not necessarily the one with a method to // report. if (!\is_string($method)) { return [McpHeader::PROTOCOL_VERSION => $protocolVersion->value, ...$this->traceHeaders($params)]; } $headers = [ McpHeader::PROTOCOL_VERSION => $protocolVersion->value, McpHeader::METHOD => $method, ...$this->traceHeaders($params), ]; if (null !== $name = McpHeader::nameFor($method, $params)) { // Tool and prompt names are only SHOULD-constrained to header-safe // characters and a resource URI is not constrained at all, so // anything unsafe is wrapped rather than dropped or mangled. $headers[McpHeader::NAME] = McpHeader::encode($name) ?? $name; } if ('tools/call' === $method && \is_string($params['name'] ?? null)) { $arguments = \is_array($params['arguments'] ?? null) ? $params['arguments'] : []; foreach ($this->tools->headersFor($params['name'], $arguments) as $suffix => $value) { $headers[McpHeader::PARAM_PREFIX.$suffix] = $value; } } return $headers; } /** * Mirrors W3C trace context from `_meta` onto its native HTTP headers, so * an application that only knows how to set `_meta` — the transport-agnostic * surface — still gets header-based propagation for free on HTTP. * * @param array|null $params * * @return array */ private function traceHeaders(?array $params): array { $meta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; $headers = []; foreach (RequestMeta::TRACE_KEYS as $key) { if (\is_string($meta[$key] ?? null)) { $headers[$key] = $meta[$key]; } } return $headers; } }