toolcraft 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +77 -59
  4. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.d.ts +2 -0
  5. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +15 -0
  6. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.d.ts +2 -0
  7. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +13 -0
  8. package/node_modules/@poe-code/agent-defs/dist/agents/codex.d.ts +2 -0
  9. package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +14 -0
  10. package/node_modules/@poe-code/agent-defs/dist/agents/goose.d.ts +2 -0
  11. package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +14 -0
  12. package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +7 -0
  13. package/node_modules/@poe-code/agent-defs/dist/agents/index.js +7 -0
  14. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.d.ts +2 -0
  15. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +15 -0
  16. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.d.ts +2 -0
  17. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +14 -0
  18. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.d.ts +2 -0
  19. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +13 -0
  20. package/node_modules/@poe-code/agent-defs/dist/index.d.ts +5 -0
  21. package/node_modules/@poe-code/agent-defs/dist/index.js +3 -0
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +3 -0
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +26 -0
  24. package/node_modules/@poe-code/agent-defs/dist/specifier.d.ts +7 -0
  25. package/node_modules/@poe-code/agent-defs/dist/specifier.js +27 -0
  26. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +16 -0
  27. package/node_modules/@poe-code/agent-defs/dist/types.js +1 -0
  28. package/node_modules/@poe-code/agent-defs/package.json +20 -0
  29. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +5 -0
  30. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +552 -0
  31. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +17 -0
  32. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +58 -0
  33. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.d.ts +7 -0
  34. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +46 -0
  35. package/node_modules/@poe-code/config-mutations/dist/formats/index.d.ts +13 -0
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +49 -0
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +31 -0
  38. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +140 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/toml.d.ts +2 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +72 -0
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.d.ts +2 -0
  42. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +73 -0
  43. package/node_modules/@poe-code/config-mutations/dist/fs-utils.d.ts +18 -0
  44. package/node_modules/@poe-code/config-mutations/dist/fs-utils.js +45 -0
  45. package/node_modules/@poe-code/config-mutations/dist/index.d.ts +8 -0
  46. package/node_modules/@poe-code/config-mutations/dist/index.js +8 -0
  47. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
  48. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.js +34 -0
  49. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +52 -0
  50. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +46 -0
  51. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
  52. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.js +32 -0
  53. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +7 -0
  54. package/node_modules/@poe-code/config-mutations/dist/template/render.js +28 -0
  55. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.d.ts +7 -0
  56. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.js +21 -0
  57. package/node_modules/@poe-code/config-mutations/dist/testing/index.d.ts +3 -0
  58. package/node_modules/@poe-code/config-mutations/dist/testing/index.js +2 -0
  59. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +25 -0
  60. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +170 -0
  61. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +156 -0
  62. package/node_modules/@poe-code/config-mutations/dist/types.js +6 -0
  63. package/node_modules/@poe-code/config-mutations/package.json +33 -0
  64. package/node_modules/@poe-code/file-lock/README.md +52 -0
  65. package/node_modules/@poe-code/file-lock/dist/index.d.ts +1 -0
  66. package/node_modules/@poe-code/file-lock/dist/index.js +1 -0
  67. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +27 -0
  68. package/node_modules/@poe-code/file-lock/dist/lock.js +203 -0
  69. package/node_modules/@poe-code/file-lock/package.json +23 -0
  70. package/node_modules/auth-store/README.md +47 -0
  71. package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
  72. package/node_modules/auth-store/dist/create-secret-store.js +35 -0
  73. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +39 -0
  74. package/node_modules/auth-store/dist/encrypted-file-store.js +156 -0
  75. package/node_modules/auth-store/dist/index.d.ts +7 -0
  76. package/node_modules/auth-store/dist/index.js +4 -0
  77. package/node_modules/auth-store/dist/keychain-store.d.ts +22 -0
  78. package/node_modules/auth-store/dist/keychain-store.js +111 -0
  79. package/node_modules/auth-store/dist/provider-store.d.ts +10 -0
  80. package/node_modules/auth-store/dist/provider-store.js +28 -0
  81. package/node_modules/auth-store/dist/types.d.ts +20 -0
  82. package/node_modules/auth-store/dist/types.js +1 -0
  83. package/node_modules/auth-store/package.json +25 -0
  84. package/node_modules/mcp-oauth/README.md +31 -0
  85. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
  86. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +97 -0
  87. package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
  88. package/node_modules/mcp-oauth/dist/client/authorization-state.js +34 -0
  89. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
  90. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +491 -0
  91. package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
  92. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +169 -0
  93. package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
  94. package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
  95. package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
  96. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +143 -0
  97. package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
  98. package/node_modules/mcp-oauth/dist/client/types.js +1 -0
  99. package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
  100. package/node_modules/mcp-oauth/dist/index.js +7 -0
  101. package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
  102. package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
  103. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +27 -0
  104. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +259 -0
  105. package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
  106. package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
  107. package/node_modules/mcp-oauth/package.json +31 -0
  108. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +4 -0
  109. package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
  110. package/node_modules/tiny-mcp-client/dist/index.js +1 -0
  111. package/node_modules/tiny-mcp-client/dist/internal.d.ts +547 -0
  112. package/node_modules/tiny-mcp-client/dist/internal.js +2404 -0
  113. package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.d.ts +1 -0
  114. package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.js +37 -0
  115. package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.d.ts +1 -0
  116. package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.js +50 -0
  117. package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.d.ts +1 -0
  118. package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.js +50 -0
  119. package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.d.ts +1 -0
  120. package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.js +51 -0
  121. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.d.ts +1 -0
  122. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +89 -0
  123. package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.d.ts +1 -0
  124. package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.js +56 -0
  125. package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.d.ts +1 -0
  126. package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.js +145 -0
  127. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +24 -0
  128. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +385 -0
  129. package/node_modules/tiny-mcp-client/package.json +22 -0
  130. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +823 -0
  131. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +882 -0
  132. package/node_modules/tiny-mcp-client/src/index.ts +94 -0
  133. package/node_modules/tiny-mcp-client/src/internal.ts +3566 -0
  134. package/node_modules/tiny-mcp-client/src/jsonrpc-types.compile-check.ts +66 -0
  135. package/node_modules/tiny-mcp-client/src/mcp-client-http-transport.integration.test.ts +222 -0
  136. package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +1294 -0
  137. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +143 -0
  138. package/node_modules/tiny-mcp-client/src/mcp-lifecycle-types.compile-check.ts +65 -0
  139. package/node_modules/tiny-mcp-client/src/mcp-prompt-types.compile-check.ts +66 -0
  140. package/node_modules/tiny-mcp-client/src/mcp-resource-types.compile-check.ts +70 -0
  141. package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +117 -0
  142. package/node_modules/tiny-mcp-client/src/mcp-transport-types.compile-check.ts +75 -0
  143. package/node_modules/tiny-mcp-client/src/mcp-utility-types.compile-check.ts +181 -0
  144. package/node_modules/tiny-mcp-client/src/mock-servers.test.ts +980 -0
  145. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +583 -0
  146. package/node_modules/tiny-mcp-client/src/transports.test.ts +8139 -0
  147. package/node_modules/tiny-mcp-client/src/utilities.test.ts +372 -0
  148. package/node_modules/tiny-mcp-client/tsconfig.json +11 -0
  149. package/package.json +24 -11
@@ -0,0 +1,1294 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ ERROR_INTERNAL,
4
+ McpClient,
5
+ McpError,
6
+ createMockCompletionServer,
7
+ createMockEchoToolServer,
8
+ createMockErrorServer,
9
+ createMockFullFeaturedServer,
10
+ createMockLoggingServer,
11
+ createMockMultiToolServer,
12
+ createMockPaginatedToolsServer,
13
+ createMockProgressServer,
14
+ createMockPromptServer,
15
+ createMockResourceServer,
16
+ createMockRootsServer,
17
+ createMockSamplingServer,
18
+ createMockSlowToolServer,
19
+ createMockSubscribableResourceServer,
20
+ createSdkTestPair,
21
+ } from "./internal.js";
22
+
23
+ const waitFor = async (predicate: () => boolean, message?: string): Promise<void> => {
24
+ const timeoutAt = Date.now() + 1_000;
25
+
26
+ while (!predicate()) {
27
+ if (Date.now() >= timeoutAt) {
28
+ throw new Error(message ?? "Timed out waiting for predicate");
29
+ }
30
+
31
+ await new Promise((resolve) => setTimeout(resolve, 5));
32
+ }
33
+ };
34
+
35
+ describe("McpClient SDK integration ping", () => {
36
+ it("connects to the mock echo server and completes a ping round-trip", async () => {
37
+ const server = await createMockEchoToolServer();
38
+ const { client, cleanup } = await createSdkTestPair(server, () =>
39
+ new McpClient({
40
+ clientInfo: {
41
+ name: "test-client",
42
+ version: "1.0.0",
43
+ },
44
+ })
45
+ );
46
+
47
+ try {
48
+ await expect(client.ping()).resolves.toBeUndefined();
49
+ } finally {
50
+ await cleanup();
51
+ }
52
+ });
53
+ });
54
+
55
+ describe("McpClient SDK integration listTools", () => {
56
+ it("lists tools from the mock echo server", async () => {
57
+ const server = await createMockEchoToolServer();
58
+ const { client, cleanup } = await createSdkTestPair(server, () =>
59
+ new McpClient({
60
+ clientInfo: {
61
+ name: "test-client",
62
+ version: "1.0.0",
63
+ },
64
+ })
65
+ );
66
+
67
+ try {
68
+ const result = await client.listTools();
69
+
70
+ expect(result.tools).toHaveLength(1);
71
+ expect(result.tools[0]).toMatchObject({
72
+ name: "echo",
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: {
76
+ message: {
77
+ type: "string",
78
+ },
79
+ },
80
+ required: ["message"],
81
+ },
82
+ });
83
+ expect(result.nextCursor).toBeUndefined();
84
+ } finally {
85
+ await cleanup();
86
+ }
87
+ });
88
+
89
+ it("returns first tools page with nextCursor when called without cursor", async () => {
90
+ const server = await createMockPaginatedToolsServer();
91
+ const { client, cleanup } = await createSdkTestPair(server, () =>
92
+ new McpClient({
93
+ clientInfo: {
94
+ name: "test-client",
95
+ version: "1.0.0",
96
+ },
97
+ })
98
+ );
99
+
100
+ try {
101
+ const firstPage = await client.listTools();
102
+
103
+ expect(firstPage.tools.map((tool) => tool.name)).toEqual([
104
+ "tool-1",
105
+ "tool-2",
106
+ "tool-3",
107
+ "tool-4",
108
+ "tool-5",
109
+ ]);
110
+ expect(firstPage.nextCursor).toBe("5");
111
+ } finally {
112
+ await cleanup();
113
+ }
114
+ });
115
+
116
+ it("returns the next tools page when called with nextCursor", async () => {
117
+ const server = await createMockPaginatedToolsServer();
118
+ const { client, cleanup } = await createSdkTestPair(server, () =>
119
+ new McpClient({
120
+ clientInfo: {
121
+ name: "test-client",
122
+ version: "1.0.0",
123
+ },
124
+ })
125
+ );
126
+
127
+ try {
128
+ const firstPage = await client.listTools();
129
+ if (firstPage.nextCursor === undefined) {
130
+ throw new Error("Expected nextCursor on first tools page");
131
+ }
132
+
133
+ const secondPage = await client.listTools({ cursor: firstPage.nextCursor });
134
+
135
+ expect(secondPage.tools.map((tool) => tool.name)).toEqual([
136
+ "tool-6",
137
+ "tool-7",
138
+ "tool-8",
139
+ "tool-9",
140
+ "tool-10",
141
+ ]);
142
+ expect(secondPage.nextCursor).toBe("10");
143
+ } finally {
144
+ await cleanup();
145
+ }
146
+ });
147
+
148
+ it("iterates all pages and collects all tools", async () => {
149
+ const server = await createMockPaginatedToolsServer();
150
+ const { client, cleanup } = await createSdkTestPair(server, () =>
151
+ new McpClient({
152
+ clientInfo: {
153
+ name: "test-client",
154
+ version: "1.0.0",
155
+ },
156
+ })
157
+ );
158
+
159
+ try {
160
+ const collectedToolNames: string[] = [];
161
+ let cursor: string | undefined;
162
+
163
+ do {
164
+ const page =
165
+ cursor === undefined
166
+ ? await client.listTools()
167
+ : await client.listTools({ cursor });
168
+ collectedToolNames.push(...page.tools.map((tool) => tool.name));
169
+ cursor = page.nextCursor;
170
+ } while (cursor !== undefined);
171
+
172
+ expect(collectedToolNames).toHaveLength(20);
173
+ expect(collectedToolNames).toEqual(
174
+ Array.from({ length: 20 }, (_, index) => `tool-${index + 1}`)
175
+ );
176
+ } finally {
177
+ await cleanup();
178
+ }
179
+ });
180
+ });
181
+
182
+ describe("McpClient SDK integration callTool", () => {
183
+ it("returns text content array for the echo tool", async () => {
184
+ const server = await createMockEchoToolServer();
185
+ const { client, cleanup } = await createSdkTestPair(server, () =>
186
+ new McpClient({
187
+ clientInfo: {
188
+ name: "test-client",
189
+ version: "1.0.0",
190
+ },
191
+ })
192
+ );
193
+
194
+ try {
195
+ const result = await client.callTool({
196
+ name: "echo",
197
+ arguments: {
198
+ message: "hello from test",
199
+ },
200
+ });
201
+
202
+ expect(result).toEqual({
203
+ content: [{ type: "text", text: "hello from test" }],
204
+ });
205
+ } finally {
206
+ await cleanup();
207
+ }
208
+ });
209
+
210
+ it("returns text content with the sum for the add tool", async () => {
211
+ const server = await createMockMultiToolServer();
212
+ const { client, cleanup } = await createSdkTestPair(server, () =>
213
+ new McpClient({
214
+ clientInfo: {
215
+ name: "test-client",
216
+ version: "1.0.0",
217
+ },
218
+ })
219
+ );
220
+
221
+ try {
222
+ const result = await client.callTool({
223
+ name: "add",
224
+ arguments: { a: 7, b: 5 },
225
+ });
226
+
227
+ expect(result).toEqual({
228
+ content: [{ type: "text", text: "12" }],
229
+ });
230
+ } finally {
231
+ await cleanup();
232
+ }
233
+ });
234
+
235
+ it("returns isError=true for tool error results", async () => {
236
+ const server = await createMockErrorServer();
237
+ const { client, cleanup } = await createSdkTestPair(server, () =>
238
+ new McpClient({
239
+ clientInfo: {
240
+ name: "test-client",
241
+ version: "1.0.0",
242
+ },
243
+ })
244
+ );
245
+
246
+ try {
247
+ const result = await client.callTool({
248
+ name: "is_error",
249
+ });
250
+
251
+ expect(result).toEqual({
252
+ isError: true,
253
+ content: [{ type: "text", text: "Intentional isError tool failure." }],
254
+ });
255
+ } finally {
256
+ await cleanup();
257
+ }
258
+ });
259
+
260
+ it("cancels an in-flight slow tool call and surfaces abort rejection", async () => {
261
+ const server = await createMockSlowToolServer({ delayMs: 1_000, pollIntervalMs: 5 });
262
+ const { client, cleanup } = await createSdkTestPair(server, () =>
263
+ new McpClient({
264
+ clientInfo: {
265
+ name: "test-client",
266
+ version: "1.0.0",
267
+ },
268
+ })
269
+ );
270
+ const abortController = new AbortController();
271
+ const abortReason = "user cancelled slow tool";
272
+
273
+ try {
274
+ const callPromise = client.callTool(
275
+ {
276
+ name: "slow",
277
+ arguments: {
278
+ delayMs: 50,
279
+ },
280
+ },
281
+ { signal: abortController.signal }
282
+ );
283
+
284
+ await waitFor(() => server.wasStarted(), "Timed out waiting for slow tool to start");
285
+ abortController.abort(abortReason);
286
+
287
+ await expect(callPromise).rejects.toBe(abortReason);
288
+ await waitFor(() => server.wasCancelled(), "Timed out waiting for slow tool cancellation");
289
+ expect(server.wasCancelled()).toBe(true);
290
+ expect(server.getCancelledRequestIds()).toHaveLength(1);
291
+ } finally {
292
+ await cleanup();
293
+ }
294
+ });
295
+
296
+ it("rejects with JSON-RPC error code and message for unknown tool names", async () => {
297
+ const server = await createMockErrorServer();
298
+ const { client, cleanup } = await createSdkTestPair(server, () =>
299
+ new McpClient({
300
+ clientInfo: {
301
+ name: "test-client",
302
+ version: "1.0.0",
303
+ },
304
+ })
305
+ );
306
+
307
+ try {
308
+ const callPromise = client.callTool({
309
+ name: "missing_tool",
310
+ });
311
+
312
+ await expect(callPromise).rejects.toBeInstanceOf(McpError);
313
+ await expect(callPromise).rejects.toMatchObject({
314
+ code: ERROR_INTERNAL,
315
+ message: "Unknown tool: missing_tool",
316
+ });
317
+ } finally {
318
+ await cleanup();
319
+ }
320
+ });
321
+ });
322
+
323
+ describe("McpClient integration tools with SDK multi-tool server", () => {
324
+ it("lists add, greet, and fail tools", async () => {
325
+ const server = await createMockMultiToolServer();
326
+ const { client, cleanup } = await createSdkTestPair(server, () =>
327
+ new McpClient({
328
+ clientInfo: {
329
+ name: "test-client",
330
+ version: "1.0.0",
331
+ },
332
+ })
333
+ );
334
+
335
+ try {
336
+ const result = await client.listTools();
337
+ const toolNames = result.tools.map((tool) => tool.name);
338
+
339
+ expect(result.tools).toHaveLength(3);
340
+ expect(toolNames).toContain("add");
341
+ expect(toolNames).toContain("greet");
342
+ expect(toolNames).toContain("fail");
343
+ } finally {
344
+ await cleanup();
345
+ }
346
+ });
347
+
348
+ it("returns 5 when calling add with a=2 and b=3", async () => {
349
+ const server = await createMockMultiToolServer();
350
+ const { client, cleanup } = await createSdkTestPair(server, () =>
351
+ new McpClient({
352
+ clientInfo: {
353
+ name: "test-client",
354
+ version: "1.0.0",
355
+ },
356
+ })
357
+ );
358
+
359
+ try {
360
+ const result = await client.callTool({
361
+ name: "add",
362
+ arguments: { a: 2, b: 3 },
363
+ });
364
+
365
+ expect(result).toEqual({
366
+ content: [{ type: "text", text: "5" }],
367
+ });
368
+ } finally {
369
+ await cleanup();
370
+ }
371
+ });
372
+
373
+ it("returns greeting text when calling greet with name=world", async () => {
374
+ const server = await createMockMultiToolServer();
375
+ const { client, cleanup } = await createSdkTestPair(server, () =>
376
+ new McpClient({
377
+ clientInfo: {
378
+ name: "test-client",
379
+ version: "1.0.0",
380
+ },
381
+ })
382
+ );
383
+
384
+ try {
385
+ const result = await client.callTool({
386
+ name: "greet",
387
+ arguments: { name: "world" },
388
+ });
389
+
390
+ expect(result).toEqual({
391
+ content: [{ type: "text", text: "Hello, world!" }],
392
+ });
393
+ } finally {
394
+ await cleanup();
395
+ }
396
+ });
397
+
398
+ it("returns isError=true when calling fail", async () => {
399
+ const server = await createMockMultiToolServer();
400
+ const { client, cleanup } = await createSdkTestPair(server, () =>
401
+ new McpClient({
402
+ clientInfo: {
403
+ name: "test-client",
404
+ version: "1.0.0",
405
+ },
406
+ })
407
+ );
408
+
409
+ try {
410
+ const result = await client.callTool({
411
+ name: "fail",
412
+ });
413
+
414
+ expect(result).toEqual({
415
+ isError: true,
416
+ content: [{ type: "text", text: "Intentional tool failure." }],
417
+ });
418
+ } finally {
419
+ await cleanup();
420
+ }
421
+ });
422
+ });
423
+
424
+ describe("McpClient SDK integration logging", () => {
425
+ it("sets logging level and dispatches onLog for matching severities", async () => {
426
+ const receivedLogs: Array<{ level: string; logger?: string; data: unknown }> = [];
427
+ const server = await createMockLoggingServer();
428
+ const { client, cleanup } = await createSdkTestPair(server, () =>
429
+ new McpClient({
430
+ clientInfo: {
431
+ name: "test-client",
432
+ version: "1.0.0",
433
+ },
434
+ onLog: async (message) => {
435
+ receivedLogs.push(message);
436
+ },
437
+ })
438
+ );
439
+
440
+ try {
441
+ await client.setLogLevel("info");
442
+
443
+ const callResult = await client.callTool({
444
+ name: "emit_logs",
445
+ });
446
+ expect(callResult).toMatchObject({
447
+ content: [{ type: "text", text: "Emitted log messages." }],
448
+ });
449
+
450
+ await waitFor(() => receivedLogs.length >= 2, "Timed out waiting for logging callback notifications");
451
+
452
+ expect(receivedLogs).toEqual([
453
+ {
454
+ level: "info",
455
+ logger: "mock-logging-server",
456
+ data: {
457
+ message: "Info message",
458
+ },
459
+ },
460
+ {
461
+ level: "error",
462
+ logger: "mock-logging-server",
463
+ data: {
464
+ message: "Error message",
465
+ },
466
+ },
467
+ ]);
468
+ } finally {
469
+ await cleanup();
470
+ }
471
+ });
472
+
473
+ it("sets logging level to error and only receives error+ log notifications", async () => {
474
+ const receivedLogs: Array<{ level: string; logger?: string; data: unknown }> = [];
475
+ const server = await createMockLoggingServer();
476
+ const { client, cleanup } = await createSdkTestPair(server, () =>
477
+ new McpClient({
478
+ clientInfo: {
479
+ name: "test-client",
480
+ version: "1.0.0",
481
+ },
482
+ onLog: async (message) => {
483
+ receivedLogs.push(message);
484
+ },
485
+ })
486
+ );
487
+
488
+ try {
489
+ await client.setLogLevel("error");
490
+
491
+ const callResult = await client.callTool({
492
+ name: "emit_logs",
493
+ });
494
+ expect(callResult).toMatchObject({
495
+ content: [{ type: "text", text: "Emitted log messages." }],
496
+ });
497
+
498
+ await waitFor(() => receivedLogs.length >= 1, "Timed out waiting for logging callback notifications");
499
+ await new Promise((resolve) => setTimeout(resolve, 25));
500
+
501
+ expect(receivedLogs).toEqual([
502
+ {
503
+ level: "error",
504
+ logger: "mock-logging-server",
505
+ data: {
506
+ message: "Error message",
507
+ },
508
+ },
509
+ ]);
510
+ } finally {
511
+ await cleanup();
512
+ }
513
+ });
514
+ });
515
+
516
+ describe("McpClient SDK integration resources", () => {
517
+ it("lists resources and reads text resource content", async () => {
518
+ const server = await createMockResourceServer();
519
+ const { client, cleanup } = await createSdkTestPair(server, () =>
520
+ new McpClient({
521
+ clientInfo: {
522
+ name: "test-client",
523
+ version: "1.0.0",
524
+ },
525
+ })
526
+ );
527
+
528
+ try {
529
+ const listResult = await client.listResources();
530
+ const textResource = listResult.resources.find(
531
+ (resource) => resource.mimeType === "text/plain"
532
+ );
533
+
534
+ expect(textResource).toBeDefined();
535
+ if (textResource === undefined) {
536
+ throw new Error("Expected text resource from resources/list");
537
+ }
538
+
539
+ const readResult = await client.readResource({ uri: textResource.uri });
540
+
541
+ expect(readResult.contents).toEqual([
542
+ {
543
+ uri: "file:///readme.txt",
544
+ mimeType: "text/plain",
545
+ text: "This is a mock README resource.",
546
+ },
547
+ ]);
548
+ } finally {
549
+ await cleanup();
550
+ }
551
+ });
552
+
553
+ it("lists resources and reads binary resource blob", async () => {
554
+ const server = await createMockResourceServer();
555
+ const { client, cleanup } = await createSdkTestPair(server, () =>
556
+ new McpClient({
557
+ clientInfo: {
558
+ name: "test-client",
559
+ version: "1.0.0",
560
+ },
561
+ })
562
+ );
563
+
564
+ try {
565
+ const listResult = await client.listResources();
566
+ const binaryResource = listResult.resources.find(
567
+ (resource) => resource.mimeType === "image/png"
568
+ );
569
+
570
+ expect(binaryResource).toBeDefined();
571
+ if (binaryResource === undefined) {
572
+ throw new Error("Expected binary resource from resources/list");
573
+ }
574
+
575
+ const readResult = await client.readResource({ uri: binaryResource.uri });
576
+
577
+ expect(readResult.contents).toEqual([
578
+ {
579
+ uri: "file:///image.png",
580
+ mimeType: "image/png",
581
+ blob: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8Xw8AAoMBgL9qj3QAAAAASUVORK5CYII=",
582
+ },
583
+ ]);
584
+ } finally {
585
+ await cleanup();
586
+ }
587
+ });
588
+
589
+ it("lists resource templates and exposes uriTemplate", async () => {
590
+ const server = await createMockResourceServer();
591
+ const { client, cleanup } = await createSdkTestPair(server, () =>
592
+ new McpClient({
593
+ clientInfo: {
594
+ name: "test-client",
595
+ version: "1.0.0",
596
+ },
597
+ })
598
+ );
599
+
600
+ try {
601
+ const result = await client.listResourceTemplates();
602
+
603
+ expect(result.resourceTemplates).toEqual([
604
+ {
605
+ uriTemplate: "file:///{path}",
606
+ name: "file-template",
607
+ },
608
+ ]);
609
+ } finally {
610
+ await cleanup();
611
+ }
612
+ });
613
+
614
+ it("subscribes to a resource and receives updated notification", async () => {
615
+ const resourceUri = "file:///readme.txt";
616
+ const updatedUris: string[] = [];
617
+ const server = await createMockSubscribableResourceServer();
618
+ const { client, cleanup } = await createSdkTestPair(server, () =>
619
+ new McpClient({
620
+ clientInfo: {
621
+ name: "test-client",
622
+ version: "1.0.0",
623
+ },
624
+ onResourceUpdated: async (uri) => {
625
+ updatedUris.push(uri);
626
+ },
627
+ })
628
+ );
629
+
630
+ try {
631
+ await client.subscribe(resourceUri);
632
+ await server.triggerResourceUpdated(resourceUri);
633
+
634
+ await waitFor(() => updatedUris.includes(resourceUri), "Timed out waiting for resource update notification");
635
+ expect(updatedUris).toEqual([resourceUri]);
636
+ } finally {
637
+ await cleanup();
638
+ }
639
+ });
640
+
641
+ it("unsubscribes from a resource and does not receive further updates", async () => {
642
+ const resourceUri = "file:///readme.txt";
643
+ const updatedUris: string[] = [];
644
+ const server = await createMockSubscribableResourceServer();
645
+ const { client, cleanup } = await createSdkTestPair(server, () =>
646
+ new McpClient({
647
+ clientInfo: {
648
+ name: "test-client",
649
+ version: "1.0.0",
650
+ },
651
+ onResourceUpdated: async (uri) => {
652
+ updatedUris.push(uri);
653
+ },
654
+ })
655
+ );
656
+
657
+ try {
658
+ await client.subscribe(resourceUri);
659
+ await server.triggerResourceUpdated(resourceUri);
660
+ await waitFor(() => updatedUris.length === 1, "Timed out waiting for resource update notification");
661
+
662
+ await client.unsubscribe(resourceUri);
663
+ updatedUris.length = 0;
664
+
665
+ await server.triggerResourceUpdated(resourceUri);
666
+ await new Promise((resolve) => setTimeout(resolve, 20));
667
+
668
+ expect(updatedUris).toEqual([]);
669
+ } finally {
670
+ await cleanup();
671
+ }
672
+ });
673
+
674
+ it("runs resource subscription lifecycle and receives notifications in order", async () => {
675
+ const resourceUri = "file:///readme.txt";
676
+ const notifications: string[] = [];
677
+ const server = await createMockSubscribableResourceServer();
678
+ const { client, cleanup } = await createSdkTestPair(server, () =>
679
+ new McpClient({
680
+ clientInfo: {
681
+ name: "test-client",
682
+ version: "1.0.0",
683
+ },
684
+ onResourcesChanged: async () => {
685
+ notifications.push("notifications/resources/list_changed");
686
+ },
687
+ onResourceUpdated: async (uri) => {
688
+ notifications.push(`notifications/resources/updated:${uri}`);
689
+ },
690
+ })
691
+ );
692
+
693
+ try {
694
+ const listResult = await client.listResources();
695
+ expect(listResult.resources).toEqual([
696
+ {
697
+ uri: resourceUri,
698
+ name: "readme.txt",
699
+ mimeType: "text/plain",
700
+ },
701
+ ]);
702
+
703
+ await client.subscribe(resourceUri);
704
+ await server.triggerResourceListChanged();
705
+ await server.triggerResourceUpdated(resourceUri, "Updated resource text after subscribe.");
706
+ await waitFor(() => notifications.length === 2, "Timed out waiting for resource update notification");
707
+
708
+ const readResult = await client.readResource({ uri: resourceUri });
709
+ expect(readResult.contents).toEqual([
710
+ {
711
+ uri: resourceUri,
712
+ mimeType: "text/plain",
713
+ text: "Updated resource text after subscribe.",
714
+ },
715
+ ]);
716
+ expect(notifications).toEqual([
717
+ "notifications/resources/list_changed",
718
+ `notifications/resources/updated:${resourceUri}`,
719
+ ]);
720
+
721
+ await client.unsubscribe(resourceUri);
722
+ await server.triggerResourceUpdated(resourceUri, "Ignored resource text after unsubscribe.");
723
+ await new Promise((resolve) => setTimeout(resolve, 20));
724
+
725
+ expect(notifications).toEqual([
726
+ "notifications/resources/list_changed",
727
+ `notifications/resources/updated:${resourceUri}`,
728
+ ]);
729
+ } finally {
730
+ await cleanup();
731
+ }
732
+ });
733
+ });
734
+
735
+ describe("McpClient SDK integration prompts", () => {
736
+ it("lists prompts and gets code_review with arguments", async () => {
737
+ const server = await createMockPromptServer();
738
+ const { client, cleanup } = await createSdkTestPair(server, () =>
739
+ new McpClient({
740
+ clientInfo: {
741
+ name: "test-client",
742
+ version: "1.0.0",
743
+ },
744
+ })
745
+ );
746
+
747
+ try {
748
+ const listResult = await client.listPrompts();
749
+ const codeReviewPrompt = listResult.prompts.find(
750
+ (prompt) => prompt.name === "code_review"
751
+ );
752
+
753
+ expect(codeReviewPrompt).toMatchObject({
754
+ name: "code_review",
755
+ arguments: [
756
+ {
757
+ name: "code",
758
+ required: true,
759
+ },
760
+ ],
761
+ });
762
+
763
+ const getResult = await client.getPrompt({
764
+ name: "code_review",
765
+ arguments: {
766
+ code: "const answer = 42;",
767
+ },
768
+ });
769
+
770
+ expect(getResult).toEqual({
771
+ description: "Review code for correctness and maintainability.",
772
+ messages: [
773
+ {
774
+ role: "user",
775
+ content: {
776
+ type: "text",
777
+ text: "Please review the following code:\nconst answer = 42;",
778
+ },
779
+ },
780
+ {
781
+ role: "assistant",
782
+ content: {
783
+ type: "text",
784
+ text: "I will review the code for potential issues and improvements.",
785
+ },
786
+ },
787
+ ],
788
+ });
789
+ } finally {
790
+ await cleanup();
791
+ }
792
+ });
793
+
794
+ it("lists prompts and gets summarize without arguments", async () => {
795
+ const server = await createMockPromptServer();
796
+ const { client, cleanup } = await createSdkTestPair(server, () =>
797
+ new McpClient({
798
+ clientInfo: {
799
+ name: "test-client",
800
+ version: "1.0.0",
801
+ },
802
+ })
803
+ );
804
+
805
+ try {
806
+ const listResult = await client.listPrompts();
807
+ const summarizePrompt = listResult.prompts.find(
808
+ (prompt) => prompt.name === "summarize"
809
+ );
810
+
811
+ expect(summarizePrompt).toMatchObject({
812
+ name: "summarize",
813
+ description: "Summarize the provided text.",
814
+ });
815
+ expect(summarizePrompt?.arguments).toBeUndefined();
816
+
817
+ const getResult = await client.getPrompt({
818
+ name: "summarize",
819
+ });
820
+
821
+ expect(getResult).toEqual({
822
+ description: "Summarize the provided text.",
823
+ messages: [
824
+ {
825
+ role: "user",
826
+ content: {
827
+ type: "text",
828
+ text: "Please summarize the provided text.",
829
+ },
830
+ },
831
+ ],
832
+ });
833
+ } finally {
834
+ await cleanup();
835
+ }
836
+ });
837
+ });
838
+
839
+ describe("McpClient SDK integration roots", () => {
840
+ it("serves roots/list during a tool call", async () => {
841
+ const roots = [
842
+ { uri: "file:///workspace", name: "workspace" },
843
+ { uri: "file:///workspace/docs" },
844
+ ];
845
+ let rootsRequestCount = 0;
846
+ const server = await createMockRootsServer();
847
+ const { client, cleanup } = await createSdkTestPair(server, () =>
848
+ new McpClient({
849
+ clientInfo: {
850
+ name: "test-client",
851
+ version: "1.0.0",
852
+ },
853
+ onRootsList: async () => {
854
+ rootsRequestCount += 1;
855
+ return roots;
856
+ },
857
+ })
858
+ );
859
+
860
+ try {
861
+ const result = await client.callTool({ name: "roots_summary" });
862
+
863
+ expect(rootsRequestCount).toBe(1);
864
+ expect(result).toMatchObject({
865
+ content: [
866
+ {
867
+ type: "text",
868
+ text: "Roots: workspace (file:///workspace), file:///workspace/docs",
869
+ },
870
+ ],
871
+ });
872
+ } finally {
873
+ await cleanup();
874
+ }
875
+ });
876
+
877
+ it("re-requests roots after sendRootsChanged", async () => {
878
+ const rootsVersions = [
879
+ [{ uri: "file:///workspace", name: "workspace" }],
880
+ [
881
+ { uri: "file:///workspace", name: "workspace" },
882
+ { uri: "file:///workspace/docs", name: "docs" },
883
+ ],
884
+ ];
885
+ let rootsRequestCount = 0;
886
+ const server = await createMockRootsServer();
887
+ const { client, cleanup } = await createSdkTestPair(server, () =>
888
+ new McpClient({
889
+ clientInfo: {
890
+ name: "test-client",
891
+ version: "1.0.0",
892
+ },
893
+ capabilities: {
894
+ roots: {
895
+ listChanged: true,
896
+ },
897
+ },
898
+ onRootsList: async () => {
899
+ const nextRoots = rootsVersions[Math.min(rootsRequestCount, rootsVersions.length - 1)];
900
+ rootsRequestCount += 1;
901
+ return nextRoots;
902
+ },
903
+ })
904
+ );
905
+
906
+ try {
907
+ const firstCall = await client.callTool({ name: "roots_summary" });
908
+ expect(firstCall).toMatchObject({
909
+ content: [
910
+ {
911
+ type: "text",
912
+ text: "Roots: workspace (file:///workspace)",
913
+ },
914
+ ],
915
+ });
916
+ expect(rootsRequestCount).toBe(1);
917
+
918
+ await client.sendRootsChanged();
919
+ await waitFor(() => rootsRequestCount === 2, "Timed out waiting for roots/list refresh");
920
+
921
+ const secondCall = await client.callTool({ name: "roots_summary" });
922
+ expect(secondCall).toMatchObject({
923
+ content: [
924
+ {
925
+ type: "text",
926
+ text: "Roots: workspace (file:///workspace), docs (file:///workspace/docs)",
927
+ },
928
+ ],
929
+ });
930
+ } finally {
931
+ await cleanup();
932
+ }
933
+ });
934
+ });
935
+
936
+ describe("McpClient SDK integration sampling", () => {
937
+ it("triggers sampling/createMessage during tool call and uses sampled output", async () => {
938
+ const samplingRequests: unknown[] = [];
939
+ const server = await createMockSamplingServer();
940
+ const { client, cleanup } = await createSdkTestPair(server, () =>
941
+ new McpClient({
942
+ clientInfo: {
943
+ name: "test-client",
944
+ version: "1.0.0",
945
+ },
946
+ onSamplingRequest: async (params) => {
947
+ samplingRequests.push(params);
948
+
949
+ return {
950
+ model: "test-model",
951
+ role: "assistant",
952
+ stopReason: "endTurn",
953
+ content: {
954
+ type: "text",
955
+ text: "TypeScript adds types to JavaScript.",
956
+ },
957
+ };
958
+ },
959
+ })
960
+ );
961
+
962
+ try {
963
+ const result = await client.callTool({
964
+ name: "sample_message",
965
+ arguments: {
966
+ topic: "TypeScript",
967
+ },
968
+ });
969
+
970
+ expect(samplingRequests).toHaveLength(1);
971
+ expect(result).toEqual({
972
+ content: [
973
+ {
974
+ type: "text",
975
+ text: "Sampled response: TypeScript adds types to JavaScript.",
976
+ },
977
+ ],
978
+ });
979
+ } finally {
980
+ await cleanup();
981
+ }
982
+ });
983
+
984
+ it("forwards modelPreferences and systemPrompt in sampling/createMessage", async () => {
985
+ const samplingRequests: unknown[] = [];
986
+ const server = await createMockSamplingServer();
987
+ const { client, cleanup } = await createSdkTestPair(server, () =>
988
+ new McpClient({
989
+ clientInfo: {
990
+ name: "test-client",
991
+ version: "1.0.0",
992
+ },
993
+ onSamplingRequest: async (params) => {
994
+ samplingRequests.push(params);
995
+
996
+ return {
997
+ model: "test-model",
998
+ role: "assistant",
999
+ stopReason: "endTurn",
1000
+ content: {
1001
+ type: "text",
1002
+ text: "Forwarding works.",
1003
+ },
1004
+ };
1005
+ },
1006
+ })
1007
+ );
1008
+
1009
+ try {
1010
+ await client.callTool({
1011
+ name: "sample_message",
1012
+ arguments: {
1013
+ topic: "TypeScript",
1014
+ },
1015
+ });
1016
+
1017
+ expect(samplingRequests).toHaveLength(1);
1018
+ expect(samplingRequests[0]).toMatchObject({
1019
+ messages: [
1020
+ {
1021
+ role: "user",
1022
+ content: {
1023
+ type: "text",
1024
+ text: "Provide a concise sentence about TypeScript.",
1025
+ },
1026
+ },
1027
+ ],
1028
+ maxTokens: 64,
1029
+ modelPreferences: {
1030
+ hints: [{ name: "mock-sampling-model" }],
1031
+ speedPriority: 0.2,
1032
+ intelligencePriority: 0.9,
1033
+ },
1034
+ systemPrompt: "Return exactly one concise sentence.",
1035
+ });
1036
+ } finally {
1037
+ await cleanup();
1038
+ }
1039
+ });
1040
+ });
1041
+
1042
+ describe("McpClient SDK integration completions", () => {
1043
+ it("completes a prompt argument and returns matching suggestions", async () => {
1044
+ const server = await createMockCompletionServer();
1045
+ const { client, cleanup } = await createSdkTestPair(server, () =>
1046
+ new McpClient({
1047
+ clientInfo: {
1048
+ name: "test-client",
1049
+ version: "1.0.0",
1050
+ },
1051
+ })
1052
+ );
1053
+
1054
+ try {
1055
+ const result = await client.complete({
1056
+ ref: {
1057
+ type: "ref/prompt",
1058
+ name: "code_review",
1059
+ },
1060
+ argument: {
1061
+ name: "language",
1062
+ value: "py",
1063
+ },
1064
+ });
1065
+
1066
+ expect(result).toEqual({
1067
+ completion: {
1068
+ values: ["python", "pydantic", "pytest"],
1069
+ hasMore: true,
1070
+ total: 5,
1071
+ },
1072
+ });
1073
+ } finally {
1074
+ await cleanup();
1075
+ }
1076
+ });
1077
+ });
1078
+
1079
+ describe("McpClient SDK integration progress", () => {
1080
+ it("calls tool with progressToken, receives progress updates, then receives result", async () => {
1081
+ const progressToken = "sdk-progress-1";
1082
+ const progressUpdates: Array<{ progressToken: string | number; progress: number }> = [];
1083
+ const eventOrder: string[] = [];
1084
+ const server = await createMockProgressServer();
1085
+ const { client, cleanup } = await createSdkTestPair(server, () =>
1086
+ new McpClient({
1087
+ clientInfo: {
1088
+ name: "test-client",
1089
+ version: "1.0.0",
1090
+ },
1091
+ onProgress: async (params) => {
1092
+ progressUpdates.push({
1093
+ progressToken: params.progressToken,
1094
+ progress: params.progress,
1095
+ });
1096
+ eventOrder.push(`progress:${params.progress}`);
1097
+ },
1098
+ })
1099
+ );
1100
+
1101
+ try {
1102
+ const result = await client.callTool(
1103
+ {
1104
+ name: "slow_task",
1105
+ },
1106
+ { progressToken }
1107
+ );
1108
+ eventOrder.push("result");
1109
+
1110
+ expect(result).toEqual({
1111
+ content: [{ type: "text", text: "slow_task complete" }],
1112
+ });
1113
+ expect(progressUpdates).toHaveLength(4);
1114
+ expect(progressUpdates.map((update) => update.progressToken)).toEqual([
1115
+ progressToken,
1116
+ progressToken,
1117
+ progressToken,
1118
+ progressToken,
1119
+ ]);
1120
+ expect(eventOrder).toEqual([
1121
+ "progress:1",
1122
+ "progress:2",
1123
+ "progress:3",
1124
+ "progress:4",
1125
+ "result",
1126
+ ]);
1127
+ } finally {
1128
+ await cleanup();
1129
+ }
1130
+ });
1131
+
1132
+ it("reports increasing progress values", async () => {
1133
+ const progressToken = "sdk-progress-2";
1134
+ const observedProgressValues: number[] = [];
1135
+ const server = await createMockProgressServer();
1136
+ const { client, cleanup } = await createSdkTestPair(server, () =>
1137
+ new McpClient({
1138
+ clientInfo: {
1139
+ name: "test-client",
1140
+ version: "1.0.0",
1141
+ },
1142
+ onProgress: async (params) => {
1143
+ observedProgressValues.push(params.progress);
1144
+ },
1145
+ })
1146
+ );
1147
+
1148
+ try {
1149
+ const result = await client.callTool(
1150
+ {
1151
+ name: "slow_task",
1152
+ },
1153
+ { progressToken }
1154
+ );
1155
+
1156
+ expect(result).toEqual({
1157
+ content: [{ type: "text", text: "slow_task complete" }],
1158
+ });
1159
+ expect(observedProgressValues).toHaveLength(4);
1160
+
1161
+ for (let index = 1; index < observedProgressValues.length; index += 1) {
1162
+ expect(observedProgressValues[index]).toBeGreaterThan(observedProgressValues[index - 1]);
1163
+ }
1164
+ } finally {
1165
+ await cleanup();
1166
+ }
1167
+ });
1168
+ });
1169
+
1170
+ describe("McpClient SDK integration full-featured lifecycle", () => {
1171
+ it("connects, exercises all full-featured capabilities, and closes cleanly", async () => {
1172
+ const server = await createMockFullFeaturedServer();
1173
+ const { client, cleanup } = await createSdkTestPair(server, () =>
1174
+ new McpClient({
1175
+ clientInfo: {
1176
+ name: "test-client",
1177
+ version: "1.0.0",
1178
+ },
1179
+ })
1180
+ );
1181
+
1182
+ try {
1183
+ expect(client.state).toBe("ready");
1184
+ expect(client.serverInfo).toEqual({
1185
+ name: "mock-full-featured-server",
1186
+ version: "1.0.0",
1187
+ });
1188
+ expect(client.serverCapabilities).toMatchObject({
1189
+ tools: {},
1190
+ resources: {},
1191
+ prompts: {},
1192
+ logging: {},
1193
+ completions: {},
1194
+ });
1195
+
1196
+ const toolsResult = await client.listTools();
1197
+ expect(toolsResult.tools).toEqual([
1198
+ {
1199
+ name: "full_featured_ping",
1200
+ description: "Returns a text response and emits an info log.",
1201
+ inputSchema: {
1202
+ type: "object",
1203
+ properties: {},
1204
+ additionalProperties: false,
1205
+ },
1206
+ },
1207
+ ]);
1208
+
1209
+ const resourcesResult = await client.listResources();
1210
+ expect(resourcesResult.resources).toEqual([
1211
+ {
1212
+ uri: "file:///full-featured.txt",
1213
+ name: "full-featured.txt",
1214
+ mimeType: "text/plain",
1215
+ },
1216
+ ]);
1217
+
1218
+ const promptsResult = await client.listPrompts();
1219
+ expect(promptsResult.prompts).toEqual([
1220
+ {
1221
+ name: "full_featured_prompt",
1222
+ description: "Returns a short prompt message for a topic.",
1223
+ arguments: [
1224
+ {
1225
+ name: "topic",
1226
+ description: "Topic to include in the prompt output.",
1227
+ required: false,
1228
+ },
1229
+ ],
1230
+ },
1231
+ ]);
1232
+
1233
+ const toolResult = await client.callTool({
1234
+ name: "full_featured_ping",
1235
+ arguments: {},
1236
+ });
1237
+ expect(toolResult).toEqual({
1238
+ content: [{ type: "text", text: "full_featured_ping ok" }],
1239
+ });
1240
+
1241
+ const resourceResult = await client.readResource({
1242
+ uri: "file:///full-featured.txt",
1243
+ });
1244
+ expect(resourceResult).toEqual({
1245
+ contents: [
1246
+ {
1247
+ uri: "file:///full-featured.txt",
1248
+ mimeType: "text/plain",
1249
+ text: "Mock full-featured resource",
1250
+ },
1251
+ ],
1252
+ });
1253
+
1254
+ const promptResult = await client.getPrompt({
1255
+ name: "full_featured_prompt",
1256
+ arguments: {
1257
+ topic: "beta",
1258
+ },
1259
+ });
1260
+ expect(promptResult).toEqual({
1261
+ description: "Mock prompt from full-featured server.",
1262
+ messages: [
1263
+ {
1264
+ role: "user",
1265
+ content: {
1266
+ type: "text",
1267
+ text: "Provide a short summary for beta.",
1268
+ },
1269
+ },
1270
+ ],
1271
+ });
1272
+
1273
+ const completionResult = await client.complete({
1274
+ ref: {
1275
+ type: "ref/prompt",
1276
+ name: "full_featured_prompt",
1277
+ },
1278
+ argument: {
1279
+ name: "topic",
1280
+ value: "b",
1281
+ },
1282
+ });
1283
+ expect(completionResult).toEqual({
1284
+ completion: {
1285
+ values: ["beta"],
1286
+ },
1287
+ });
1288
+ } finally {
1289
+ await cleanup();
1290
+ }
1291
+
1292
+ expect(client.state).toBe("closed");
1293
+ });
1294
+ });