toolcraft 0.0.50 → 0.0.52

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 (47) hide show
  1. package/node_modules/@poe-code/agent-defs/README.md +35 -0
  2. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +9 -2
  3. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +3 -1
  4. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +44 -2
  5. package/node_modules/@poe-code/agent-human-in-loop/package.json +0 -1
  6. package/node_modules/@poe-code/agent-mcp-config/README.md +54 -0
  7. package/node_modules/@poe-code/agent-mcp-config/package.json +0 -2
  8. package/node_modules/@poe-code/config-mutations/README.md +55 -0
  9. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +1 -0
  10. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +65 -0
  11. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +4 -11
  12. package/node_modules/@poe-code/config-mutations/package.json +0 -1
  13. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +17 -0
  14. package/node_modules/@poe-code/frontmatter/dist/fences.js +33 -5
  15. package/node_modules/@poe-code/frontmatter/dist/parse.js +86 -14
  16. package/node_modules/@poe-code/frontmatter/dist/stringify.js +13 -0
  17. package/node_modules/@poe-code/process-runner/dist/docker/args.js +14 -1
  18. package/node_modules/@poe-code/process-runner/dist/docker/build-context.d.ts +5 -0
  19. package/node_modules/@poe-code/process-runner/dist/docker/build-context.js +37 -0
  20. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +29 -29
  21. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  22. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  23. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +49 -3
  24. package/node_modules/@poe-code/process-runner/package.json +8 -1
  25. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +7 -0
  26. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +34 -7
  27. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +75 -19
  28. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  29. package/node_modules/@poe-code/task-list/dist/backends/utils.js +23 -2
  30. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +16 -12
  31. package/node_modules/@poe-code/task-list/dist/state-machine.js +9 -0
  32. package/node_modules/@poe-code/task-list/package.json +0 -1
  33. package/node_modules/auth-store/dist/create-secret-store.js +4 -3
  34. package/node_modules/auth-store/dist/encrypted-file-store.js +49 -2
  35. package/node_modules/auth-store/dist/keychain-store.js +11 -4
  36. package/node_modules/auth-store/package.json +0 -1
  37. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +69 -12
  38. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +100 -68
  39. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +19 -18
  40. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +37 -31
  41. package/node_modules/mcp-oauth/package.json +0 -1
  42. package/node_modules/tiny-mcp-client/dist/internal.js +96 -10
  43. package/node_modules/tiny-mcp-client/package.json +0 -3
  44. package/node_modules/tiny-mcp-client/src/internal.ts +120 -18
  45. package/node_modules/tiny-mcp-client/src/transports.test.ts +231 -2
  46. package/node_modules/toolcraft-design/package.json +0 -1
  47. package/package.json +3 -2
@@ -71,7 +71,8 @@ export class McpClient {
71
71
  await onToolsChanged();
72
72
  });
73
73
  messageLayer.onNotification("notifications/resources/list_changed", async () => {
74
- if (onResourcesChanged === undefined) {
74
+ if (onResourcesChanged === undefined
75
+ || this.currentServerCapabilities?.resources?.listChanged !== true) {
75
76
  return;
76
77
  }
77
78
  await onResourcesChanged();
@@ -90,7 +91,8 @@ export class McpClient {
90
91
  await onResourceUpdated(uri);
91
92
  });
92
93
  messageLayer.onNotification("notifications/prompts/list_changed", async () => {
93
- if (onPromptsChanged === undefined) {
94
+ if (onPromptsChanged === undefined
95
+ || this.currentServerCapabilities?.prompts?.listChanged !== true) {
94
96
  return;
95
97
  }
96
98
  await onPromptsChanged();
@@ -325,7 +327,11 @@ export class McpClient {
325
327
  throw new Error("Server does not support resources");
326
328
  }
327
329
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
328
- return (await messageLayer.sendRequest("resources/list", requestParams));
330
+ const result = await messageLayer.sendRequest("resources/list", requestParams);
331
+ if (!isResourcesListResult(result)) {
332
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/list result");
333
+ }
334
+ return result;
329
335
  }
330
336
  async listResourceTemplates(params = {}) {
331
337
  const messageLayer = this.getMessageLayerOrThrow();
@@ -334,7 +340,11 @@ export class McpClient {
334
340
  throw new Error("Server does not support resources");
335
341
  }
336
342
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
337
- return (await messageLayer.sendRequest("resources/templates/list", requestParams));
343
+ const result = await messageLayer.sendRequest("resources/templates/list", requestParams);
344
+ if (!isResourceTemplatesListResult(result)) {
345
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/templates/list result");
346
+ }
347
+ return result;
338
348
  }
339
349
  async readResource(params) {
340
350
  const messageLayer = this.getMessageLayerOrThrow();
@@ -342,7 +352,11 @@ export class McpClient {
342
352
  if (serverCapabilities.resources === undefined) {
343
353
  throw new Error("Server does not support resources");
344
354
  }
345
- return (await messageLayer.sendRequest("resources/read", params));
355
+ const result = await messageLayer.sendRequest("resources/read", params);
356
+ if (!isReadResourceResult(result)) {
357
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/read result");
358
+ }
359
+ return result;
346
360
  }
347
361
  async subscribe(uri) {
348
362
  const messageLayer = this.getMessageLayerOrThrow();
@@ -377,7 +391,11 @@ export class McpClient {
377
391
  if (serverCapabilities.prompts === undefined) {
378
392
  throw new Error("Server does not support prompts");
379
393
  }
380
- return (await messageLayer.sendRequest("prompts/get", params));
394
+ const result = await messageLayer.sendRequest("prompts/get", params);
395
+ if (!isGetPromptResult(result)) {
396
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid prompts/get result");
397
+ }
398
+ return result;
381
399
  }
382
400
  async complete(params) {
383
401
  const messageLayer = this.getMessageLayerOrThrow();
@@ -385,7 +403,11 @@ export class McpClient {
385
403
  if (serverCapabilities.completions === undefined) {
386
404
  throw new Error("Server does not support completions");
387
405
  }
388
- return (await messageLayer.sendRequest("completion/complete", params));
406
+ const result = await messageLayer.sendRequest("completion/complete", params);
407
+ if (!isCompleteResult(result)) {
408
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid completion/complete result");
409
+ }
410
+ return result;
389
411
  }
390
412
  async setLogLevel(level) {
391
413
  const messageLayer = this.getMessageLayerOrThrow();
@@ -2434,6 +2456,72 @@ function isToolsListResult(value) {
2434
2456
  && Array.isArray(value.tools)
2435
2457
  && (value.nextCursor === undefined || typeof value.nextCursor === "string");
2436
2458
  }
2459
+ function isResourcesListResult(value) {
2460
+ return isObjectRecord(value)
2461
+ && Array.isArray(value.resources)
2462
+ && value.resources.every(isResource)
2463
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
2464
+ }
2465
+ function isResourceTemplatesListResult(value) {
2466
+ return isObjectRecord(value)
2467
+ && Array.isArray(value.resourceTemplates)
2468
+ && value.resourceTemplates.every(isResourceTemplate)
2469
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
2470
+ }
2471
+ function isReadResourceResult(value) {
2472
+ return isObjectRecord(value)
2473
+ && Array.isArray(value.contents)
2474
+ && value.contents.every(isResourceContents);
2475
+ }
2476
+ function isGetPromptResult(value) {
2477
+ return isObjectRecord(value)
2478
+ && (value.description === undefined || typeof value.description === "string")
2479
+ && Array.isArray(value.messages)
2480
+ && value.messages.every(isPromptMessage);
2481
+ }
2482
+ function isCompleteResult(value) {
2483
+ return isObjectRecord(value)
2484
+ && isObjectRecord(value.completion)
2485
+ && Array.isArray(value.completion.values)
2486
+ && value.completion.values.every((candidate) => typeof candidate === "string")
2487
+ && (value.completion.hasMore === undefined || typeof value.completion.hasMore === "boolean")
2488
+ && (value.completion.total === undefined || typeof value.completion.total === "number");
2489
+ }
2490
+ function isResource(value) {
2491
+ return isObjectRecord(value)
2492
+ && typeof value.uri === "string"
2493
+ && typeof value.name === "string"
2494
+ && (value.description === undefined || typeof value.description === "string")
2495
+ && (value.mimeType === undefined || typeof value.mimeType === "string")
2496
+ && (value.size === undefined || typeof value.size === "number");
2497
+ }
2498
+ function isResourceTemplate(value) {
2499
+ return isObjectRecord(value)
2500
+ && typeof value.uriTemplate === "string"
2501
+ && typeof value.name === "string"
2502
+ && (value.description === undefined || typeof value.description === "string")
2503
+ && (value.mimeType === undefined || typeof value.mimeType === "string");
2504
+ }
2505
+ function isResourceContents(value) {
2506
+ if (!isObjectRecord(value) || typeof value.uri !== "string") {
2507
+ return false;
2508
+ }
2509
+ if (value.mimeType !== undefined && typeof value.mimeType !== "string") {
2510
+ return false;
2511
+ }
2512
+ const hasText = value.text !== undefined;
2513
+ const hasBlob = value.blob !== undefined;
2514
+ if (!hasText && !hasBlob) {
2515
+ return false;
2516
+ }
2517
+ return (!hasText || typeof value.text === "string")
2518
+ && (!hasBlob || typeof value.blob === "string");
2519
+ }
2520
+ function isPromptMessage(value) {
2521
+ return isObjectRecord(value)
2522
+ && (value.role === "user" || value.role === "assistant")
2523
+ && isContentItem(value.content);
2524
+ }
2437
2525
  function isContentItem(value) {
2438
2526
  if (!isObjectRecord(value)) {
2439
2527
  return false;
@@ -2447,9 +2535,7 @@ function isContentItem(value) {
2447
2535
  if (value.type !== "resource" || !isObjectRecord(value.resource)) {
2448
2536
  return false;
2449
2537
  }
2450
- return typeof value.resource.uri === "string"
2451
- && (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
2452
- && (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
2538
+ return isResourceContents(value.resource);
2453
2539
  }
2454
2540
  function hasOwn(value, property) {
2455
2541
  return Object.prototype.hasOwnProperty.call(value, property);
@@ -13,9 +13,6 @@
13
13
  "url": "git+https://github.com/poe-platform/poe-code.git",
14
14
  "directory": "packages/tiny-mcp-client"
15
15
  },
16
- "dependencies": {
17
- "mcp-oauth": "*"
18
- },
19
16
  "devDependencies": {
20
17
  "@modelcontextprotocol/sdk": "^1.26.0",
21
18
  "tiny-stdio-mcp-server": "*"
@@ -213,7 +213,10 @@ export class McpClient {
213
213
  await onToolsChanged();
214
214
  });
215
215
  messageLayer.onNotification("notifications/resources/list_changed", async () => {
216
- if (onResourcesChanged === undefined) {
216
+ if (
217
+ onResourcesChanged === undefined
218
+ || this.currentServerCapabilities?.resources?.listChanged !== true
219
+ ) {
217
220
  return;
218
221
  }
219
222
 
@@ -236,7 +239,10 @@ export class McpClient {
236
239
  await onResourceUpdated(uri);
237
240
  });
238
241
  messageLayer.onNotification("notifications/prompts/list_changed", async () => {
239
- if (onPromptsChanged === undefined) {
242
+ if (
243
+ onPromptsChanged === undefined
244
+ || this.currentServerCapabilities?.prompts?.listChanged !== true
245
+ ) {
240
246
  return;
241
247
  }
242
248
 
@@ -521,10 +527,12 @@ export class McpClient {
521
527
  }
522
528
 
523
529
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
524
- return (await messageLayer.sendRequest("resources/list", requestParams)) as {
525
- resources: Resource[];
526
- nextCursor?: string;
527
- };
530
+ const result = await messageLayer.sendRequest("resources/list", requestParams);
531
+ if (!isResourcesListResult(result)) {
532
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/list result");
533
+ }
534
+
535
+ return result;
528
536
  }
529
537
 
530
538
  async listResourceTemplates(
@@ -537,10 +545,12 @@ export class McpClient {
537
545
  }
538
546
 
539
547
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
540
- return (await messageLayer.sendRequest("resources/templates/list", requestParams)) as {
541
- resourceTemplates: ResourceTemplate[];
542
- nextCursor?: string;
543
- };
548
+ const result = await messageLayer.sendRequest("resources/templates/list", requestParams);
549
+ if (!isResourceTemplatesListResult(result)) {
550
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/templates/list result");
551
+ }
552
+
553
+ return result;
544
554
  }
545
555
 
546
556
  async readResource(params: ReadResourceParams): Promise<{ contents: ResourceContents[] }> {
@@ -550,9 +560,12 @@ export class McpClient {
550
560
  throw new Error("Server does not support resources");
551
561
  }
552
562
 
553
- return (await messageLayer.sendRequest("resources/read", params)) as {
554
- contents: ResourceContents[];
555
- };
563
+ const result = await messageLayer.sendRequest("resources/read", params);
564
+ if (!isReadResourceResult(result)) {
565
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid resources/read result");
566
+ }
567
+
568
+ return result;
556
569
  }
557
570
 
558
571
  async subscribe(uri: string): Promise<void> {
@@ -598,7 +611,12 @@ export class McpClient {
598
611
  throw new Error("Server does not support prompts");
599
612
  }
600
613
 
601
- return (await messageLayer.sendRequest("prompts/get", params)) as GetPromptResult;
614
+ const result = await messageLayer.sendRequest("prompts/get", params);
615
+ if (!isGetPromptResult(result)) {
616
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid prompts/get result");
617
+ }
618
+
619
+ return result;
602
620
  }
603
621
 
604
622
  async complete(params: CompleteParams): Promise<CompleteResult> {
@@ -608,7 +626,12 @@ export class McpClient {
608
626
  throw new Error("Server does not support completions");
609
627
  }
610
628
 
611
- return (await messageLayer.sendRequest("completion/complete", params)) as CompleteResult;
629
+ const result = await messageLayer.sendRequest("completion/complete", params);
630
+ if (!isCompleteResult(result)) {
631
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid completion/complete result");
632
+ }
633
+
634
+ return result;
612
635
  }
613
636
 
614
637
  async setLogLevel(level: LogLevel): Promise<void> {
@@ -3597,6 +3620,87 @@ function isToolsListResult(value: unknown): value is { tools: Tool[]; nextCursor
3597
3620
  && (value.nextCursor === undefined || typeof value.nextCursor === "string");
3598
3621
  }
3599
3622
 
3623
+ function isResourcesListResult(value: unknown): value is { resources: Resource[]; nextCursor?: string } {
3624
+ return isObjectRecord(value)
3625
+ && Array.isArray(value.resources)
3626
+ && value.resources.every(isResource)
3627
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
3628
+ }
3629
+
3630
+ function isResourceTemplatesListResult(
3631
+ value: unknown
3632
+ ): value is { resourceTemplates: ResourceTemplate[]; nextCursor?: string } {
3633
+ return isObjectRecord(value)
3634
+ && Array.isArray(value.resourceTemplates)
3635
+ && value.resourceTemplates.every(isResourceTemplate)
3636
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
3637
+ }
3638
+
3639
+ function isReadResourceResult(value: unknown): value is { contents: ResourceContents[] } {
3640
+ return isObjectRecord(value)
3641
+ && Array.isArray(value.contents)
3642
+ && value.contents.every(isResourceContents);
3643
+ }
3644
+
3645
+ function isGetPromptResult(value: unknown): value is GetPromptResult {
3646
+ return isObjectRecord(value)
3647
+ && (value.description === undefined || typeof value.description === "string")
3648
+ && Array.isArray(value.messages)
3649
+ && value.messages.every(isPromptMessage);
3650
+ }
3651
+
3652
+ function isCompleteResult(value: unknown): value is CompleteResult {
3653
+ return isObjectRecord(value)
3654
+ && isObjectRecord(value.completion)
3655
+ && Array.isArray(value.completion.values)
3656
+ && value.completion.values.every((candidate) => typeof candidate === "string")
3657
+ && (value.completion.hasMore === undefined || typeof value.completion.hasMore === "boolean")
3658
+ && (value.completion.total === undefined || typeof value.completion.total === "number");
3659
+ }
3660
+
3661
+ function isResource(value: unknown): value is Resource {
3662
+ return isObjectRecord(value)
3663
+ && typeof value.uri === "string"
3664
+ && typeof value.name === "string"
3665
+ && (value.description === undefined || typeof value.description === "string")
3666
+ && (value.mimeType === undefined || typeof value.mimeType === "string")
3667
+ && (value.size === undefined || typeof value.size === "number");
3668
+ }
3669
+
3670
+ function isResourceTemplate(value: unknown): value is ResourceTemplate {
3671
+ return isObjectRecord(value)
3672
+ && typeof value.uriTemplate === "string"
3673
+ && typeof value.name === "string"
3674
+ && (value.description === undefined || typeof value.description === "string")
3675
+ && (value.mimeType === undefined || typeof value.mimeType === "string");
3676
+ }
3677
+
3678
+ function isResourceContents(value: unknown): value is ResourceContents {
3679
+ if (!isObjectRecord(value) || typeof value.uri !== "string") {
3680
+ return false;
3681
+ }
3682
+
3683
+ if (value.mimeType !== undefined && typeof value.mimeType !== "string") {
3684
+ return false;
3685
+ }
3686
+
3687
+ const hasText = value.text !== undefined;
3688
+ const hasBlob = value.blob !== undefined;
3689
+
3690
+ if (!hasText && !hasBlob) {
3691
+ return false;
3692
+ }
3693
+
3694
+ return (!hasText || typeof value.text === "string")
3695
+ && (!hasBlob || typeof value.blob === "string");
3696
+ }
3697
+
3698
+ function isPromptMessage(value: unknown): value is PromptMessage {
3699
+ return isObjectRecord(value)
3700
+ && (value.role === "user" || value.role === "assistant")
3701
+ && isContentItem(value.content);
3702
+ }
3703
+
3600
3704
  function isContentItem(value: unknown): value is ContentItem {
3601
3705
  if (!isObjectRecord(value)) {
3602
3706
  return false;
@@ -3614,9 +3718,7 @@ function isContentItem(value: unknown): value is ContentItem {
3614
3718
  return false;
3615
3719
  }
3616
3720
 
3617
- return typeof value.resource.uri === "string"
3618
- && (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
3619
- && (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
3721
+ return isResourceContents(value.resource);
3620
3722
  }
3621
3723
 
3622
3724
  function hasOwn(
@@ -3870,7 +3870,7 @@ describe("McpClient connect", () => {
3870
3870
  id: initializeRequest.id,
3871
3871
  result: {
3872
3872
  protocolVersion: "2025-03-26",
3873
- capabilities: {},
3873
+ capabilities: { resources: { listChanged: true } },
3874
3874
  serverInfo: {
3875
3875
  name: "server",
3876
3876
  version: "1.0.0",
@@ -3900,6 +3900,40 @@ describe("McpClient connect", () => {
3900
3900
  await client.close();
3901
3901
  });
3902
3902
 
3903
+ it("ignores resources/list_changed when server did not advertise listChanged", async () => {
3904
+ const onResourcesChanged = vi.fn();
3905
+ const { client, readable, iterator, connectPromise } = await startClientHandshake(
3906
+ {
3907
+ protocolVersion: "2025-03-26",
3908
+ capabilities: { resources: {} },
3909
+ serverInfo: { name: "server", version: "1.0.0" },
3910
+ },
3911
+ {
3912
+ clientInfo: {
3913
+ name: "tiny-mcp-client",
3914
+ version: "0.1.0",
3915
+ },
3916
+ onResourcesChanged,
3917
+ }
3918
+ );
3919
+ await connectPromise;
3920
+ await iterator.next();
3921
+
3922
+ readable.write(
3923
+ `${JSON.stringify({
3924
+ jsonrpc: "2.0",
3925
+ method: "notifications/resources/list_changed",
3926
+ })}\n`
3927
+ );
3928
+ await new Promise<void>((resolve) => {
3929
+ setImmediate(resolve);
3930
+ });
3931
+
3932
+ expect(onResourcesChanged).toHaveBeenCalledTimes(0);
3933
+
3934
+ await client.close();
3935
+ });
3936
+
3903
3937
  it("calls onPromptsChanged when server sends prompts/list_changed notification", async () => {
3904
3938
  const readable = new PassThrough();
3905
3939
  const writable = new PassThrough();
@@ -3942,7 +3976,7 @@ describe("McpClient connect", () => {
3942
3976
  id: initializeRequest.id,
3943
3977
  result: {
3944
3978
  protocolVersion: "2025-03-26",
3945
- capabilities: {},
3979
+ capabilities: { prompts: { listChanged: true } },
3946
3980
  serverInfo: {
3947
3981
  name: "server",
3948
3982
  version: "1.0.0",
@@ -3972,6 +4006,40 @@ describe("McpClient connect", () => {
3972
4006
  await client.close();
3973
4007
  });
3974
4008
 
4009
+ it("ignores prompts/list_changed when server did not advertise listChanged", async () => {
4010
+ const onPromptsChanged = vi.fn();
4011
+ const { client, readable, iterator, connectPromise } = await startClientHandshake(
4012
+ {
4013
+ protocolVersion: "2025-03-26",
4014
+ capabilities: { prompts: {} },
4015
+ serverInfo: { name: "server", version: "1.0.0" },
4016
+ },
4017
+ {
4018
+ clientInfo: {
4019
+ name: "tiny-mcp-client",
4020
+ version: "0.1.0",
4021
+ },
4022
+ onPromptsChanged,
4023
+ }
4024
+ );
4025
+ await connectPromise;
4026
+ await iterator.next();
4027
+
4028
+ readable.write(
4029
+ `${JSON.stringify({
4030
+ jsonrpc: "2.0",
4031
+ method: "notifications/prompts/list_changed",
4032
+ })}\n`
4033
+ );
4034
+ await new Promise<void>((resolve) => {
4035
+ setImmediate(resolve);
4036
+ });
4037
+
4038
+ expect(onPromptsChanged).toHaveBeenCalledTimes(0);
4039
+
4040
+ await client.close();
4041
+ });
4042
+
3975
4043
  it("calls onResourceUpdated with uri when server sends resources/updated notification", async () => {
3976
4044
  const readable = new PassThrough();
3977
4045
  const writable = new PassThrough();
@@ -5685,6 +5753,36 @@ describe("McpClient listResources", () => {
5685
5753
  nextCursor: "4",
5686
5754
  });
5687
5755
  });
5756
+
5757
+ it("rejects invalid resource descriptors returned from resources/list", async () => {
5758
+ const { client, readable, iterator, connectPromise } = await startClientHandshake({
5759
+ protocolVersion: "2025-03-26",
5760
+ capabilities: { resources: {} },
5761
+ serverInfo: { name: "server", version: "1.0.0" },
5762
+ });
5763
+ await connectPromise;
5764
+ await iterator.next();
5765
+
5766
+ const requestPromise = client.listResources();
5767
+ const requestLine = await iterator.next();
5768
+ if (requestLine.done) {
5769
+ throw new Error("Expected resources/list request line to be written");
5770
+ }
5771
+ const request = JSON.parse(requestLine.value) as { id: number };
5772
+ readable.write(
5773
+ `${JSON.stringify({
5774
+ jsonrpc: "2.0",
5775
+ id: request.id,
5776
+ result: {
5777
+ resources: [{ uri: 42, name: null }],
5778
+ nextCursor: 7,
5779
+ },
5780
+ })}\n`
5781
+ );
5782
+
5783
+ await expect(requestPromise).rejects.toThrow("Invalid resources/list result");
5784
+ await client.close();
5785
+ });
5688
5786
  });
5689
5787
 
5690
5788
  describe("McpClient listResourceTemplates", () => {
@@ -5774,6 +5872,36 @@ describe("McpClient listResourceTemplates", () => {
5774
5872
  resourceTemplates: expectedResourceTemplates,
5775
5873
  });
5776
5874
  });
5875
+
5876
+ it("rejects invalid resource templates returned from resources/templates/list", async () => {
5877
+ const { client, readable, iterator, connectPromise } = await startClientHandshake({
5878
+ protocolVersion: "2025-03-26",
5879
+ capabilities: { resources: {} },
5880
+ serverInfo: { name: "server", version: "1.0.0" },
5881
+ });
5882
+ await connectPromise;
5883
+ await iterator.next();
5884
+
5885
+ const requestPromise = client.listResourceTemplates();
5886
+ const requestLine = await iterator.next();
5887
+ if (requestLine.done) {
5888
+ throw new Error("Expected resources/templates/list request line to be written");
5889
+ }
5890
+ const request = JSON.parse(requestLine.value) as { id: number };
5891
+ readable.write(
5892
+ `${JSON.stringify({
5893
+ jsonrpc: "2.0",
5894
+ id: request.id,
5895
+ result: {
5896
+ resourceTemplates: [{ uriTemplate: 123, name: false, mimeType: 42 }],
5897
+ nextCursor: 7,
5898
+ },
5899
+ })}\n`
5900
+ );
5901
+
5902
+ await expect(requestPromise).rejects.toThrow("Invalid resources/templates/list result");
5903
+ await client.close();
5904
+ });
5777
5905
  });
5778
5906
 
5779
5907
  describe("McpClient listPrompts", () => {
@@ -6188,6 +6316,40 @@ describe("McpClient getPrompt", () => {
6188
6316
  new Set(["user", "assistant"])
6189
6317
  );
6190
6318
  });
6319
+
6320
+ it("rejects invalid prompt messages returned from prompts/get", async () => {
6321
+ const { client, readable, iterator, connectPromise } = await startClientHandshake({
6322
+ protocolVersion: "2025-03-26",
6323
+ capabilities: { prompts: {} },
6324
+ serverInfo: { name: "server", version: "1.0.0" },
6325
+ });
6326
+ await connectPromise;
6327
+ await iterator.next();
6328
+
6329
+ const requestPromise = client.getPrompt({ name: "broken" });
6330
+ const requestLine = await iterator.next();
6331
+ if (requestLine.done) {
6332
+ throw new Error("Expected prompts/get request line to be written");
6333
+ }
6334
+ const request = JSON.parse(requestLine.value) as { id: number };
6335
+ readable.write(
6336
+ `${JSON.stringify({
6337
+ jsonrpc: "2.0",
6338
+ id: request.id,
6339
+ result: {
6340
+ messages: [
6341
+ {
6342
+ role: "system",
6343
+ content: { type: "text", text: 123 },
6344
+ },
6345
+ ],
6346
+ },
6347
+ })}\n`
6348
+ );
6349
+
6350
+ await expect(requestPromise).rejects.toThrow("Invalid prompts/get result");
6351
+ await client.close();
6352
+ });
6191
6353
  });
6192
6354
 
6193
6355
  describe("McpClient complete", () => {
@@ -6482,6 +6644,44 @@ describe("McpClient complete", () => {
6482
6644
 
6483
6645
  await expect(completePromise).resolves.toEqual(expectedResult);
6484
6646
  });
6647
+
6648
+ it("rejects invalid completion values returned from completion/complete", async () => {
6649
+ const { client, readable, iterator, connectPromise } = await startClientHandshake({
6650
+ protocolVersion: "2025-03-26",
6651
+ capabilities: { completions: {} },
6652
+ serverInfo: { name: "server", version: "1.0.0" },
6653
+ });
6654
+ await connectPromise;
6655
+ await iterator.next();
6656
+
6657
+ const requestPromise = client.complete({
6658
+ ref: {
6659
+ type: "ref/prompt",
6660
+ name: "code_review",
6661
+ },
6662
+ argument: {
6663
+ name: "language",
6664
+ value: "p",
6665
+ },
6666
+ });
6667
+ const requestLine = await iterator.next();
6668
+ if (requestLine.done) {
6669
+ throw new Error("Expected completion/complete request line to be written");
6670
+ }
6671
+ const request = JSON.parse(requestLine.value) as { id: number };
6672
+ readable.write(
6673
+ `${JSON.stringify({
6674
+ jsonrpc: "2.0",
6675
+ id: request.id,
6676
+ result: {
6677
+ completion: { values: "not-an-array" },
6678
+ },
6679
+ })}\n`
6680
+ );
6681
+
6682
+ await expect(requestPromise).rejects.toThrow("Invalid completion/complete result");
6683
+ await client.close();
6684
+ });
6485
6685
  });
6486
6686
 
6487
6687
  describe("McpClient readResource", () => {
@@ -6575,6 +6775,35 @@ describe("McpClient readResource", () => {
6575
6775
  });
6576
6776
  });
6577
6777
 
6778
+ it("rejects invalid content returned from resources/read", async () => {
6779
+ const { client, readable, iterator, connectPromise } = await startClientHandshake({
6780
+ protocolVersion: "2025-03-26",
6781
+ capabilities: { resources: {} },
6782
+ serverInfo: { name: "server", version: "1.0.0" },
6783
+ });
6784
+ await connectPromise;
6785
+ await iterator.next();
6786
+
6787
+ const requestPromise = client.readResource({ uri: "memo://bad" });
6788
+ const requestLine = await iterator.next();
6789
+ if (requestLine.done) {
6790
+ throw new Error("Expected resources/read request line to be written");
6791
+ }
6792
+ const request = JSON.parse(requestLine.value) as { id: number };
6793
+ readable.write(
6794
+ `${JSON.stringify({
6795
+ jsonrpc: "2.0",
6796
+ id: request.id,
6797
+ result: {
6798
+ contents: [{ uri: "memo://bad", mimeType: 123, text: 456 }],
6799
+ },
6800
+ })}\n`
6801
+ );
6802
+
6803
+ await expect(requestPromise).rejects.toThrow("Invalid resources/read result");
6804
+ await client.close();
6805
+ });
6806
+
6578
6807
  it("sends resources/read with uri and returns binary resource contents", async () => {
6579
6808
  const readable = new PassThrough();
6580
6809
  const writable = new PassThrough();
@@ -20,7 +20,6 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@poe-code/frontmatter": "*",
24
23
  "fast-string-width": "^3.0.2",
25
24
  "fast-wrap-ansi": "^0.2.0",
26
25
  "sisteransi": "^1.0.5"