|
1 | 1 | import { renderHook, act } from "@testing-library/react"; |
2 | 2 | import { useConnection } from "../useConnection"; |
3 | 3 | import { z } from "zod"; |
4 | | -import { ClientRequest } from "@modelcontextprotocol/sdk/types.js"; |
| 4 | +import { |
| 5 | + ClientRequest, |
| 6 | + JSONRPCMessage, |
| 7 | +} from "@modelcontextprotocol/sdk/types.js"; |
5 | 8 | import { DEFAULT_INSPECTOR_CONFIG, CLIENT_IDENTITY } from "../../constants"; |
6 | 9 | import { |
7 | 10 | SSEClientTransportOptions, |
@@ -42,10 +45,12 @@ const mockSSETransport: { |
42 | 45 | start: jest.Mock; |
43 | 46 | url: URL | undefined; |
44 | 47 | options: SSEClientTransportOptions | undefined; |
| 48 | + onmessage?: (message: JSONRPCMessage) => void; |
45 | 49 | } = { |
46 | 50 | start: jest.fn(), |
47 | 51 | url: undefined, |
48 | 52 | options: undefined, |
| 53 | + onmessage: undefined, |
49 | 54 | }; |
50 | 55 |
|
51 | 56 | const mockStreamableHTTPTransport: { |
@@ -482,6 +487,129 @@ describe("useConnection", () => { |
482 | 487 | }); |
483 | 488 | }); |
484 | 489 |
|
| 490 | + describe("Ref Resolution", () => { |
| 491 | + beforeEach(() => { |
| 492 | + jest.clearAllMocks(); |
| 493 | + }); |
| 494 | + |
| 495 | + test("resolves $ref references in requestedSchema properties before validation", async () => { |
| 496 | + const mockProtocolOnMessage = jest.fn(); |
| 497 | + |
| 498 | + mockSSETransport.onmessage = mockProtocolOnMessage; |
| 499 | + |
| 500 | + const { result } = renderHook(() => useConnection(defaultProps)); |
| 501 | + |
| 502 | + await act(async () => { |
| 503 | + await result.current.connect(); |
| 504 | + }); |
| 505 | + |
| 506 | + const mockRequestWithRef: JSONRPCMessage = { |
| 507 | + jsonrpc: "2.0", |
| 508 | + id: 1, |
| 509 | + method: "elicitation/create", |
| 510 | + params: { |
| 511 | + message: "Please provide your information", |
| 512 | + requestedSchema: { |
| 513 | + type: "object", |
| 514 | + properties: { |
| 515 | + source: { |
| 516 | + type: "string", |
| 517 | + minLength: 1, |
| 518 | + title: "A Connectable Node", |
| 519 | + }, |
| 520 | + target: { |
| 521 | + $ref: "#/properties/source", |
| 522 | + }, |
| 523 | + }, |
| 524 | + }, |
| 525 | + }, |
| 526 | + }; |
| 527 | + |
| 528 | + await act(async () => { |
| 529 | + mockSSETransport.onmessage!(mockRequestWithRef); |
| 530 | + }); |
| 531 | + |
| 532 | + expect(mockProtocolOnMessage).toHaveBeenCalledTimes(1); |
| 533 | + |
| 534 | + const message = mockProtocolOnMessage.mock.calls[0][0]; |
| 535 | + expect(message.params.requestedSchema.properties.target).toEqual({ |
| 536 | + type: "string", |
| 537 | + minLength: 1, |
| 538 | + title: "A Connectable Node", |
| 539 | + }); |
| 540 | + }); |
| 541 | + |
| 542 | + test("resolves $ref references to $defs in requestedSchema", async () => { |
| 543 | + const mockProtocolOnMessage = jest.fn(); |
| 544 | + |
| 545 | + mockSSETransport.onmessage = mockProtocolOnMessage; |
| 546 | + |
| 547 | + const { result } = renderHook(() => useConnection(defaultProps)); |
| 548 | + |
| 549 | + await act(async () => { |
| 550 | + await result.current.connect(); |
| 551 | + }); |
| 552 | + |
| 553 | + const mockRequestWithDefs: JSONRPCMessage = { |
| 554 | + jsonrpc: "2.0", |
| 555 | + id: 1, |
| 556 | + method: "elicitation/create", |
| 557 | + params: { |
| 558 | + message: "Please provide your information", |
| 559 | + requestedSchema: { |
| 560 | + type: "object", |
| 561 | + properties: { |
| 562 | + user: { |
| 563 | + $ref: "#/$defs/UserInput", |
| 564 | + }, |
| 565 | + }, |
| 566 | + $defs: { |
| 567 | + UserInput: { |
| 568 | + type: "object", |
| 569 | + properties: { |
| 570 | + name: { |
| 571 | + type: "string", |
| 572 | + title: "Name", |
| 573 | + }, |
| 574 | + age: { |
| 575 | + type: "integer", |
| 576 | + title: "Age", |
| 577 | + minimum: 0, |
| 578 | + }, |
| 579 | + }, |
| 580 | + required: ["name"], |
| 581 | + }, |
| 582 | + }, |
| 583 | + }, |
| 584 | + }, |
| 585 | + }; |
| 586 | + |
| 587 | + await act(async () => { |
| 588 | + mockSSETransport.onmessage!(mockRequestWithDefs); |
| 589 | + }); |
| 590 | + |
| 591 | + expect(mockProtocolOnMessage).toHaveBeenCalledTimes(1); |
| 592 | + |
| 593 | + const message = mockProtocolOnMessage.mock.calls[0][0]; |
| 594 | + // The $ref should be resolved to the actual UserInput definition |
| 595 | + expect(message.params.requestedSchema.properties.user).toEqual({ |
| 596 | + type: "object", |
| 597 | + properties: { |
| 598 | + name: { |
| 599 | + type: "string", |
| 600 | + title: "Name", |
| 601 | + }, |
| 602 | + age: { |
| 603 | + type: "integer", |
| 604 | + title: "Age", |
| 605 | + minimum: 0, |
| 606 | + }, |
| 607 | + }, |
| 608 | + required: ["name"], |
| 609 | + }); |
| 610 | + }); |
| 611 | + }); |
| 612 | + |
485 | 613 | describe("URL Port Handling", () => { |
486 | 614 | const SSEClientTransport = jest.requireMock( |
487 | 615 | "@modelcontextprotocol/sdk/client/sse.js", |
|
0 commit comments