From 692af64380b64a73eae08a30e262a8022278ba79 Mon Sep 17 00:00:00 2001 From: EhsanAzish80 Date: Thu, 24 Sep 2026 18:41:24 +0300 Subject: [PATCH 1/2] Add standard MCP headers to HTTP client requests --- .../Base/Transports/HTTPClientTransport.swift | 41 +++++++++++++++++ .../HTTPServer/HTTPServerTypes.swift | 2 + Tests/MCPTests/HTTPClientTransportTests.swift | 46 +++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/Sources/MCP/Base/Transports/HTTPClientTransport.swift b/Sources/MCP/Base/Transports/HTTPClientTransport.swift index 060cd133..3768079b 100644 --- a/Sources/MCP/Base/Transports/HTTPClientTransport.swift +++ b/Sources/MCP/Base/Transports/HTTPClientTransport.swift @@ -250,6 +250,7 @@ public actor HTTPClientTransport: Transport { ) request.addValue(ContentType.json, forHTTPHeaderField: HTTPHeaderName.contentType) request.httpBody = data + addStandardRequestHeaders(to: &request, body: data) if let protocolVersion = protocolVersion { request.addValue(protocolVersion, forHTTPHeaderField: HTTPHeaderName.protocolVersion) @@ -304,6 +305,46 @@ public actor HTTPClientTransport: Transport { } } + /// Mirrors routing fields from a JSON-RPC request or notification into HTTP headers. + /// Responses and legacy batches have no single method to mirror. + private func addStandardRequestHeaders(to request: inout URLRequest, body: Data) { + guard let message = try? JSONSerialization.jsonObject(with: body) as? [String: Any], + let method = message["method"] as? String, + isSafeHTTPHeaderValue(method) + else { return } + + request.setValue(method, forHTTPHeaderField: HTTPHeaderName.method) + + let nameKey: String + switch method { + case "tools/call", "prompts/get": nameKey = "name" + case "resources/read": nameKey = "uri" + default: return + } + + guard let params = message["params"] as? [String: Any], + let name = params[nameKey] as? String + else { return } + + let headerValue: String + if isSafeHTTPHeaderValue(name), + !(name.hasPrefix("=?base64?") && name.hasSuffix("?=")) + { + headerValue = name + } else { + headerValue = "=?base64?\(Data(name.utf8).base64EncodedString())?=" + } + request.setValue(headerValue, forHTTPHeaderField: HTTPHeaderName.name) + } + + private func isSafeHTTPHeaderValue(_ value: String) -> Bool { + let bytes = value.utf8 + guard let first = bytes.first, let last = bytes.last, + first != 0x20, first != 0x09, last != 0x20, last != 0x09 + else { return false } + return bytes.allSatisfy { $0 == 0x09 || (0x20...0x7E).contains($0) } + } + #if os(Linux) private func processResponse(response: URLResponse, data: Data) async throws { guard let httpResponse = response as? HTTPURLResponse else { diff --git a/Sources/MCP/Base/Transports/HTTPServer/HTTPServerTypes.swift b/Sources/MCP/Base/Transports/HTTPServer/HTTPServerTypes.swift index bd56191c..0e5b6364 100644 --- a/Sources/MCP/Base/Transports/HTTPServer/HTTPServerTypes.swift +++ b/Sources/MCP/Base/Transports/HTTPServer/HTTPServerTypes.swift @@ -144,6 +144,8 @@ public enum HTTPResponse: Sendable { public enum HTTPHeaderName { public static let sessionID = "MCP-Session-Id" public static let protocolVersion = "MCP-Protocol-Version" + public static let method = "Mcp-Method" + public static let name = "Mcp-Name" public static let lastEventID = "Last-Event-ID" public static let accept = "Accept" public static let contentType = "Content-Type" diff --git a/Tests/MCPTests/HTTPClientTransportTests.swift b/Tests/MCPTests/HTTPClientTransportTests.swift index bd8c38f7..ebe30762 100644 --- a/Tests/MCPTests/HTTPClientTransportTests.swift +++ b/Tests/MCPTests/HTTPClientTransportTests.swift @@ -214,6 +214,52 @@ import Testing #expect(receivedData == responseData) } + @Test("POST request headers mirror the JSON-RPC method and name", .httpClientTransportSetup) + func testStandardRequestHeaders() async throws { + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [MockURLProtocol.self] + + let transport = HTTPClientTransport( + endpoint: testEndpoint, + configuration: configuration, + streaming: false + ) + try await transport.connect() + + let cases: [(body: String, method: String?, name: String?)] = [ + (#"{"jsonrpc":"2.0","id":1,"method":"initialize"}"#, "initialize", nil), + (#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#, + "notifications/initialized", nil), + (#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"weather"}}"#, + "tools/call", "weather"), + (#"{"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"hello world"}}"#, + "prompts/get", "hello world"), + (#"{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"file:///資料"}}"#, + "resources/read", "=?base64?ZmlsZTovLy/os4fmlpk=?="), + (#"{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"=?base64?abc?="}}"#, + "tools/call", "=?base64?PT9iYXNlNjQ/YWJjPz0=?="), + (#"{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":" weather "}}"#, + "tools/call", "=?base64?IHdlYXRoZXIg?="), + (#"{"jsonrpc":"2.0","id":7,"result":{}}"#, nil, nil), + ] + + for testCase in cases { + await MockURLProtocol.requestHandlerStorage.setHandler { + [testEndpoint] request in + #expect(request.url == testEndpoint) + #expect(request.value(forHTTPHeaderField: "Mcp-Method") == testCase.method) + #expect(request.value(forHTTPHeaderField: "Mcp-Name") == testCase.name) + + let response = HTTPURLResponse( + url: testEndpoint, statusCode: 202, httpVersion: "HTTP/1.1", + headerFields: [:])! + return (response, Data()) + } + try await transport.send(Data(testCase.body.utf8)) + } + await transport.disconnect() + } + @Test("Send and Receive Session ID", .httpClientTransportSetup) func testSendAndReceiveSessionID() async throws { let configuration = URLSessionConfiguration.ephemeral From bcc4d95e153f28975d715d91343207b1b0c4910d Mon Sep 17 00:00:00 2001 From: EhsanAzish80 Date: Sat, 3 Oct 2026 08:59:19 +0300 Subject: [PATCH 2/2] Encode tabs in MCP HTTP headers --- Sources/MCP/Base/Transports/HTTPClientTransport.swift | 2 +- Tests/MCPTests/HTTPClientTransportTests.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/MCP/Base/Transports/HTTPClientTransport.swift b/Sources/MCP/Base/Transports/HTTPClientTransport.swift index 3768079b..332bbba9 100644 --- a/Sources/MCP/Base/Transports/HTTPClientTransport.swift +++ b/Sources/MCP/Base/Transports/HTTPClientTransport.swift @@ -342,7 +342,7 @@ public actor HTTPClientTransport: Transport { guard let first = bytes.first, let last = bytes.last, first != 0x20, first != 0x09, last != 0x20, last != 0x09 else { return false } - return bytes.allSatisfy { $0 == 0x09 || (0x20...0x7E).contains($0) } + return bytes.allSatisfy { (0x20...0x7E).contains($0) } } #if os(Linux) diff --git a/Tests/MCPTests/HTTPClientTransportTests.swift b/Tests/MCPTests/HTTPClientTransportTests.swift index ebe30762..66799c97 100644 --- a/Tests/MCPTests/HTTPClientTransportTests.swift +++ b/Tests/MCPTests/HTTPClientTransportTests.swift @@ -240,6 +240,8 @@ import Testing "tools/call", "=?base64?PT9iYXNlNjQ/YWJjPz0=?="), (#"{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":" weather "}}"#, "tools/call", "=?base64?IHdlYXRoZXIg?="), + (#"{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"foo\tbar"}}"#, + "tools/call", "=?base64?Zm9vCWJhcg==?="), (#"{"jsonrpc":"2.0","id":7,"result":{}}"#, nil, nil), ]