Skip to content

Commit dfbf10c

Browse files
committed
test: elicitation handling
1 parent 124c781 commit dfbf10c

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

client/src/lib/hooks/__tests__/useConnection.test.tsx

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { z } from "zod";
44
import { ClientRequest } from "@modelcontextprotocol/sdk/types.js";
55
import { DEFAULT_INSPECTOR_CONFIG } from "../../constants";
66
import { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
7+
import {
8+
ElicitResult,
9+
ElicitRequest,
10+
} from "@modelcontextprotocol/sdk/types.js";
711

812
// Mock fetch
913
global.fetch = jest.fn().mockResolvedValue({
@@ -198,6 +202,252 @@ describe("useConnection", () => {
198202
).rejects.toThrow("MCP client not connected");
199203
});
200204

205+
describe("Elicitation Support", () => {
206+
beforeEach(() => {
207+
jest.clearAllMocks();
208+
});
209+
210+
test("declares elicitation capability during client initialization", async () => {
211+
const Client = jest.requireMock(
212+
"@modelcontextprotocol/sdk/client/index.js",
213+
).Client;
214+
215+
const { result } = renderHook(() => useConnection(defaultProps));
216+
217+
await act(async () => {
218+
await result.current.connect();
219+
});
220+
221+
expect(Client).toHaveBeenCalledWith(
222+
expect.objectContaining({
223+
name: "mcp-inspector",
224+
version: expect.any(String),
225+
}),
226+
expect.objectContaining({
227+
capabilities: expect.objectContaining({
228+
elicitation: {},
229+
}),
230+
}),
231+
);
232+
});
233+
234+
test("sets up elicitation request handler when onElicitationRequest is provided", async () => {
235+
const mockOnElicitationRequest = jest.fn();
236+
const propsWithElicitation = {
237+
...defaultProps,
238+
onElicitationRequest: mockOnElicitationRequest,
239+
};
240+
241+
const { result } = renderHook(() => useConnection(propsWithElicitation));
242+
243+
await act(async () => {
244+
await result.current.connect();
245+
});
246+
247+
const elicitRequestHandlerCall =
248+
mockClient.setRequestHandler.mock.calls.find((call) => {
249+
try {
250+
const schema = call[0];
251+
const testRequest = {
252+
method: "elicitation/create",
253+
params: {
254+
message: "test message",
255+
requestedSchema: {
256+
type: "object",
257+
properties: {
258+
name: { type: "string" },
259+
},
260+
},
261+
},
262+
};
263+
const parseResult =
264+
schema.safeParse && schema.safeParse(testRequest);
265+
return parseResult?.success;
266+
} catch {
267+
return false;
268+
}
269+
});
270+
271+
expect(elicitRequestHandlerCall).toBeDefined();
272+
expect(mockClient.setRequestHandler).toHaveBeenCalledWith(
273+
expect.any(Object),
274+
expect.any(Function),
275+
);
276+
});
277+
278+
test("does not set up elicitation request handler when onElicitationRequest is not provided", async () => {
279+
const { result } = renderHook(() => useConnection(defaultProps));
280+
281+
await act(async () => {
282+
await result.current.connect();
283+
});
284+
285+
const elicitRequestHandlerCall =
286+
mockClient.setRequestHandler.mock.calls.find((call) => {
287+
try {
288+
const schema = call[0];
289+
const testRequest = {
290+
method: "elicitation/create",
291+
params: {
292+
message: "test message",
293+
requestedSchema: {
294+
type: "object",
295+
properties: {
296+
name: { type: "string" },
297+
},
298+
},
299+
},
300+
};
301+
const parseResult =
302+
schema.safeParse && schema.safeParse(testRequest);
303+
return parseResult?.success;
304+
} catch {
305+
return false;
306+
}
307+
});
308+
309+
expect(elicitRequestHandlerCall).toBeUndefined();
310+
});
311+
312+
test("elicitation request handler calls onElicitationRequest callback", async () => {
313+
const mockOnElicitationRequest = jest.fn();
314+
const propsWithElicitation = {
315+
...defaultProps,
316+
onElicitationRequest: mockOnElicitationRequest,
317+
};
318+
319+
const { result } = renderHook(() => useConnection(propsWithElicitation));
320+
321+
await act(async () => {
322+
await result.current.connect();
323+
});
324+
325+
const elicitRequestHandlerCall =
326+
mockClient.setRequestHandler.mock.calls.find((call) => {
327+
try {
328+
const schema = call[0];
329+
const testRequest = {
330+
method: "elicitation/create",
331+
params: {
332+
message: "test message",
333+
requestedSchema: {
334+
type: "object",
335+
properties: {
336+
name: { type: "string" },
337+
},
338+
},
339+
},
340+
};
341+
const parseResult =
342+
schema.safeParse && schema.safeParse(testRequest);
343+
return parseResult?.success;
344+
} catch {
345+
return false;
346+
}
347+
});
348+
349+
expect(elicitRequestHandlerCall).toBeDefined();
350+
const [, handler] = elicitRequestHandlerCall;
351+
352+
const mockElicitationRequest: ElicitRequest = {
353+
method: "elicitation/create",
354+
params: {
355+
message: "Please provide your name",
356+
requestedSchema: {
357+
type: "object",
358+
properties: {
359+
name: { type: "string" },
360+
},
361+
required: ["name"],
362+
},
363+
},
364+
};
365+
366+
mockOnElicitationRequest.mockImplementation((_request, resolve) => {
367+
resolve({ action: "accept", content: { name: "test" } });
368+
});
369+
370+
await act(async () => {
371+
await handler(mockElicitationRequest);
372+
});
373+
374+
expect(mockOnElicitationRequest).toHaveBeenCalledWith(
375+
mockElicitationRequest,
376+
expect.any(Function),
377+
);
378+
});
379+
380+
test("elicitation request handler returns a promise that resolves with the callback result", async () => {
381+
const mockOnElicitationRequest = jest.fn();
382+
const propsWithElicitation = {
383+
...defaultProps,
384+
onElicitationRequest: mockOnElicitationRequest,
385+
};
386+
387+
const { result } = renderHook(() => useConnection(propsWithElicitation));
388+
389+
await act(async () => {
390+
await result.current.connect();
391+
});
392+
393+
const elicitRequestHandlerCall =
394+
mockClient.setRequestHandler.mock.calls.find((call) => {
395+
try {
396+
const schema = call[0];
397+
const testRequest = {
398+
method: "elicitation/create",
399+
params: {
400+
message: "test message",
401+
requestedSchema: {
402+
type: "object",
403+
properties: {
404+
name: { type: "string" },
405+
},
406+
},
407+
},
408+
};
409+
const parseResult =
410+
schema.safeParse && schema.safeParse(testRequest);
411+
return parseResult?.success;
412+
} catch {
413+
return false;
414+
}
415+
});
416+
417+
const [, handler] = elicitRequestHandlerCall;
418+
419+
const mockElicitationRequest: ElicitRequest = {
420+
method: "elicitation/create",
421+
params: {
422+
message: "Please provide your name",
423+
requestedSchema: {
424+
type: "object",
425+
properties: {
426+
name: { type: "string" },
427+
},
428+
required: ["name"],
429+
},
430+
},
431+
};
432+
433+
const mockResponse: ElicitResult = {
434+
action: "accept",
435+
content: { name: "John Doe" },
436+
};
437+
438+
mockOnElicitationRequest.mockImplementation((_request, resolve) => {
439+
resolve(mockResponse);
440+
});
441+
442+
let handlerResult;
443+
await act(async () => {
444+
handlerResult = await handler(mockElicitationRequest);
445+
});
446+
447+
expect(handlerResult).toEqual(mockResponse);
448+
});
449+
});
450+
201451
describe("URL Port Handling", () => {
202452
const SSEClientTransport = jest.requireMock(
203453
"@modelcontextprotocol/sdk/client/sse.js",

0 commit comments

Comments
 (0)