diff --git a/.changeset/fix-unsafe-request-id.md b/.changeset/fix-unsafe-request-id.md new file mode 100644 index 000000000..adb9b163a --- /dev/null +++ b/.changeset/fix-unsafe-request-id.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/core": patch +--- + +fix: reject request IDs exceeding Number.MAX_SAFE_INTEGER diff --git a/packages/core/src/types/schemas.ts b/packages/core/src/types/schemas.ts index 309b6ade2..cca62be1e 100644 --- a/packages/core/src/types/schemas.ts +++ b/packages/core/src/types/schemas.ts @@ -119,7 +119,15 @@ export const ResultSchema = z.looseObject({ /** * A uniquely identifying ID for a request in JSON-RPC. */ -export const RequestIdSchema = z.union([z.string(), z.number().int()]); +export const RequestIdSchema = z.union([ + z.string(), + z + .number() + .int() + .refine(n => n >= Number.MIN_SAFE_INTEGER && n <= Number.MAX_SAFE_INTEGER, { + message: 'Request ID must be within Number.MAX_SAFE_INTEGER range' + }) +]); /** * A request that expects a response. diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 429b3ecdd..9222838a3 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -1,4 +1,5 @@ import { + RequestIdSchema, CallToolResultSchema, ClientCapabilitiesSchema, CompleteRequestSchema, @@ -984,3 +985,23 @@ describe('Types', () => { }); }); }); + +describe('RequestIdSchema', () => { + test('should accept string IDs', () => { + expect(RequestIdSchema.parse('abc-123')).toBe('abc-123'); + }); + + test('should accept safe integer IDs', () => { + expect(RequestIdSchema.parse(1)).toBe(1); + expect(RequestIdSchema.parse(Number.MAX_SAFE_INTEGER)).toBe(Number.MAX_SAFE_INTEGER); + }); + + test('should reject IDs exceeding MAX_SAFE_INTEGER', () => { + expect(() => RequestIdSchema.parse(Number.MAX_SAFE_INTEGER + 1)).toThrow(); + expect(() => RequestIdSchema.parse(9007199254740992)).toThrow(); + }); + + test('should reject non-integer numeric IDs', () => { + expect(() => RequestIdSchema.parse(1.5)).toThrow(); + }); +});