From 53572f4780e6659ba9bd2cac334189a2e6cbdecb Mon Sep 17 00:00:00 2001 From: JamieScanlon Date: Tue, 14 Jul 2026 21:41:52 -0700 Subject: [PATCH] Use monotonic integer IDs for ID.random by default UUID string request IDs are JSON-RPC valid but cause Apple mcpbridge to hang silently. Default to monotonic .number IDs for broader server compatibility; keep UUID generation via ID.randomUUID. Co-authored-by: Cursor --- Sources/MCP/Base/ID.swift | 21 +++++++++++++++++++-- Tests/MCPTests/IDTests.swift | 23 ++++++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Sources/MCP/Base/ID.swift b/Sources/MCP/Base/ID.swift index 271b6150..9bff70c7 100644 --- a/Sources/MCP/Base/ID.swift +++ b/Sources/MCP/Base/ID.swift @@ -1,4 +1,5 @@ import struct Foundation.UUID +import struct os.OSAllocatedUnfairLock /// A unique identifier for a request. public enum ID: Hashable, Sendable { @@ -8,9 +9,25 @@ public enum ID: Hashable, Sendable { /// A number ID. case number(Int) - /// Generates a random string ID. + /// Process-wide counter for monotonic numeric request IDs. + /// + /// Integer IDs are JSON-RPC 2.0–valid and avoid silent hangs against servers + /// that only accept numeric `id` values (e.g. Apple's `mcpbridge`). + private static let counter = OSAllocatedUnfairLock(initialState: 0) + + /// Generates a monotonic numeric ID suitable for outbound client requests. public static var random: ID { - return .string(UUID().uuidString) + .number( + counter.withLock { value in + value += 1 + return value + } + ) + } + + /// Generates a random UUID string ID (previous `random` behavior). + public static var randomUUID: ID { + .string(UUID().uuidString) } } diff --git a/Tests/MCPTests/IDTests.swift b/Tests/MCPTests/IDTests.swift index 0abf28e6..91fec9bf 100644 --- a/Tests/MCPTests/IDTests.swift +++ b/Tests/MCPTests/IDTests.swift @@ -34,18 +34,35 @@ struct IDTests { #expect(decoded == id) } - @Test("Random ID generation") + @Test("Random ID generation uses monotonic integers") func testRandomID() throws { let id1 = ID.random let id2 = ID.random #expect(id1 != id2, "Random IDs should be unique") + guard case .number(let n1) = id1, case .number(let n2) = id2 else { + Issue.record("Random ID should be number type") + return + } + #expect(n2 > n1) + + let encoder = JSONEncoder() + let data = try encoder.encode(id1) + let json = String(data: data, encoding: .utf8) + #expect(json == String(n1), "Encoded random ID should be a bare JSON number") + } + + @Test("Random UUID ID generation") + func testRandomUUIDID() throws { + let id1 = ID.randomUUID + let id2 = ID.randomUUID + #expect(id1 != id2, "UUID IDs should be unique") + if case .string(let str) = id1 { #expect(!str.isEmpty) - // Verify it's a valid UUID string #expect(UUID(uuidString: str) != nil) } else { - #expect(Bool(false), "Random ID should be string type") + Issue.record("randomUUID ID should be string type") } } }