toolcraft 0.0.22 → 0.0.24

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 (147) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +57 -14
  5. package/dist/error-report.js +32 -3
  6. package/dist/human-in-loop/approval-tasks.d.ts +1 -0
  7. package/dist/human-in-loop/approval-tasks.js +7 -5
  8. package/dist/human-in-loop/approvals-commands.js +51 -8
  9. package/dist/human-in-loop/runner.js +24 -19
  10. package/dist/human-in-loop/state-machine.d.ts +3 -3
  11. package/dist/human-in-loop/state-machine.js +13 -5
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +6 -1
  14. package/dist/mcp-proxy.js +85 -19
  15. package/dist/mcp.compile-check.js +1 -0
  16. package/dist/mcp.d.ts +1 -0
  17. package/dist/mcp.js +50 -8
  18. package/dist/renderer.js +119 -13
  19. package/dist/sdk.compile-check.js +1 -0
  20. package/dist/sdk.d.ts +1 -0
  21. package/dist/sdk.js +56 -11
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
  24. package/node_modules/@poe-code/agent-defs/package.json +1 -1
  25. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
  26. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
  27. package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
  28. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
  29. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
  30. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
  31. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
  32. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
  33. package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
  34. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
  35. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
  38. package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
  42. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
  43. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
  44. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
  45. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
  46. package/node_modules/@poe-code/config-mutations/package.json +1 -1
  47. package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
  48. package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
  49. package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
  50. package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
  51. package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
  52. package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
  53. package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
  54. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
  55. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
  56. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  57. package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
  58. package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
  59. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
  60. package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
  61. package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
  62. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
  63. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
  64. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
  65. package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
  66. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
  67. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
  68. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
  73. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
  75. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
  76. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  77. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
  78. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
  79. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
  80. package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
  81. package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
  82. package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
  83. package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
  84. package/node_modules/@poe-code/design-system/package.json +2 -1
  85. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +244 -110
  86. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +16 -4
  87. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  88. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +16 -1
  89. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  90. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  91. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  92. package/node_modules/@poe-code/process-runner/dist/types.d.ts +3 -0
  93. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +57 -0
  94. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +484 -0
  95. package/node_modules/@poe-code/process-runner/package.json +1 -1
  96. package/node_modules/@poe-code/task-list/README.md +0 -2
  97. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  98. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  99. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  100. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  101. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  102. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  103. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  104. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  105. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  106. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  107. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  108. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  109. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  110. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  111. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  112. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  113. package/node_modules/@poe-code/task-list/package.json +1 -2
  114. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  115. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
  116. package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
  117. package/node_modules/auth-store/dist/index.d.ts +1 -1
  118. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  119. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  120. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  121. package/node_modules/auth-store/dist/provider-store.js +55 -7
  122. package/node_modules/auth-store/dist/types.d.ts +3 -1
  123. package/node_modules/auth-store/package.json +2 -1
  124. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  125. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  126. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  127. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  128. package/node_modules/mcp-oauth/package.json +1 -0
  129. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  130. package/node_modules/tiny-mcp-client/dist/internal.d.ts +8 -4
  131. package/node_modules/tiny-mcp-client/dist/internal.js +237 -67
  132. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  133. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  134. package/node_modules/tiny-mcp-client/package.json +2 -1
  135. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  136. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  137. package/node_modules/tiny-mcp-client/src/internal.ts +279 -77
  138. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  139. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  140. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  141. package/package.json +10 -12
  142. package/node_modules/@poe-code/file-lock/README.md +0 -52
  143. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  144. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  145. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  146. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  147. package/node_modules/@poe-code/file-lock/package.json +0 -23
@@ -8,7 +8,7 @@ import {
8
8
  OAuthError,
9
9
  type OAuthClientProvider,
10
10
  type OAuthClientProviderOptions,
11
- } from "../../mcp-oauth/dist/index.js";
11
+ } from "mcp-oauth";
12
12
  import type { Server as TinyStdioMcpServer } from "tiny-stdio-mcp-server";
13
13
  import {
14
14
  OAuthMetadataDiscovery,
@@ -28,7 +28,7 @@ export {
28
28
  export {
29
29
  createAuthStoreSessionStore,
30
30
  createDefaultOAuthClientProvider,
31
- } from "../../mcp-oauth/dist/index.js";
31
+ } from "mcp-oauth";
32
32
  export type {
33
33
  OAuthDiscoveryCache,
34
34
  } from "./oauth-discovery.js";
@@ -39,7 +39,7 @@ export type {
39
39
  OAuthClientProviderOptions,
40
40
  OAuthSessionStore,
41
41
  StoredOAuthSession,
42
- } from "../../mcp-oauth/dist/index.js";
42
+ } from "mcp-oauth";
43
43
 
44
44
  export type RequestId = number | string;
45
45
 
@@ -97,6 +97,7 @@ export interface InitializeResult {
97
97
 
98
98
  export interface McpClientOptions {
99
99
  clientInfo: Implementation;
100
+ requestTimeoutMs?: number;
100
101
  capabilities?: ClientCapabilities;
101
102
  onToolsChanged?: () => void | Promise<void>;
102
103
  onResourcesChanged?: () => void | Promise<void>;
@@ -115,8 +116,11 @@ const MCP_PROTOCOL_VERSION = "2025-03-26";
115
116
  export class McpClient {
116
117
  private currentState: "disconnected" | "initializing" | "ready" | "closed" = "disconnected";
117
118
  private currentServerCapabilities: ServerCapabilities | null = null;
119
+ private currentClientCapabilities: ClientCapabilities | null = null;
118
120
  private currentServerInfo: Implementation | null = null;
119
121
  private currentInstructions: string | undefined;
122
+ private readonly subscribedResourceUris = new Set<string>();
123
+ private readonly activeProgressTokens = new Map<ProgressToken, number>();
120
124
  private readonly options: McpClientOptions;
121
125
  private transport: McpTransport | null = null;
122
126
  private messageLayer: JsonRpcMessageLayer | null = null;
@@ -130,7 +134,9 @@ export class McpClient {
130
134
  }
131
135
 
132
136
  get serverCapabilities(): ServerCapabilities | null {
133
- return this.currentServerCapabilities;
137
+ return this.currentServerCapabilities === null
138
+ ? null
139
+ : structuredClone(this.currentServerCapabilities);
134
140
  }
135
141
 
136
142
  get serverInfo(): Implementation | null {
@@ -162,6 +168,13 @@ export class McpClient {
162
168
  throw new Error("MCP client is already connected");
163
169
  }
164
170
 
171
+ this.currentServerCapabilities = null;
172
+ this.currentClientCapabilities = null;
173
+ this.currentServerInfo = null;
174
+ this.currentInstructions = undefined;
175
+ this.subscribedResourceUris.clear();
176
+ this.activeProgressTokens.clear();
177
+
165
178
  const transportClosedReason = transport.closed
166
179
  .then((closedEvent) => closedEvent.reason)
167
180
  .catch((error: unknown) =>
@@ -170,7 +183,7 @@ export class McpClient {
170
183
  const messageLayer = new JsonRpcMessageLayer(
171
184
  transport.readable,
172
185
  transport.writable,
173
- 30_000,
186
+ this.options.requestTimeoutMs,
174
187
  transportClosedReason
175
188
  );
176
189
  const {
@@ -192,14 +205,8 @@ export class McpClient {
192
205
  );
193
206
  }
194
207
 
195
- if (onRootsList !== undefined) {
196
- messageLayer.onRequest("roots/list", async () => ({
197
- roots: await onRootsList(),
198
- }));
199
- }
200
-
201
208
  messageLayer.onNotification("notifications/tools/list_changed", async () => {
202
- if (onToolsChanged === undefined) {
209
+ if (onToolsChanged === undefined || this.currentServerCapabilities?.tools?.listChanged !== true) {
203
210
  return;
204
211
  }
205
212
 
@@ -222,7 +229,7 @@ export class McpClient {
222
229
  }
223
230
 
224
231
  const { uri } = params as { uri?: unknown };
225
- if (typeof uri !== "string") {
232
+ if (typeof uri !== "string" || !this.subscribedResourceUris.has(uri)) {
226
233
  return;
227
234
  }
228
235
 
@@ -265,7 +272,11 @@ export class McpClient {
265
272
  }
266
273
 
267
274
  const { progressToken, progress } = params;
268
- if (!isRequestId(progressToken) || typeof progress !== "number") {
275
+ if (
276
+ !isRequestId(progressToken) ||
277
+ typeof progress !== "number" ||
278
+ !this.activeProgressTokens.has(progressToken)
279
+ ) {
269
280
  return;
270
281
  }
271
282
 
@@ -297,6 +308,8 @@ export class McpClient {
297
308
  this.transport = transport;
298
309
  this.messageLayer = messageLayer;
299
310
  this.currentState = "initializing";
311
+ this.subscribedResourceUris.clear();
312
+ this.activeProgressTokens.clear();
300
313
  transport.closed
301
314
  .then((closedEvent) => {
302
315
  if (this.transport !== transport) {
@@ -334,26 +347,52 @@ export class McpClient {
334
347
  };
335
348
  }
336
349
 
337
- const initializeResult = (await messageLayer.sendRequest("initialize", {
338
- protocolVersion: MCP_PROTOCOL_VERSION,
339
- clientInfo: this.options.clientInfo,
340
- capabilities,
341
- })) as InitializeResult;
350
+ this.currentClientCapabilities = structuredClone(capabilities);
342
351
 
343
- if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
344
- throw new McpError(
345
- ERROR_INVALID_REQUEST,
346
- `Unsupported protocol version: ${initializeResult.protocolVersion}`
347
- );
348
- }
352
+ try {
353
+ const initializeResultValue = await messageLayer.sendRequest("initialize", {
354
+ protocolVersion: MCP_PROTOCOL_VERSION,
355
+ clientInfo: this.options.clientInfo,
356
+ capabilities,
357
+ });
358
+
359
+ if (!isInitializeResult(initializeResultValue)) {
360
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid initialize result");
361
+ }
362
+
363
+ const initializeResult = initializeResultValue;
364
+
365
+ if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
366
+ throw new McpError(
367
+ ERROR_INVALID_REQUEST,
368
+ `Unsupported protocol version: ${initializeResult.protocolVersion}`
369
+ );
370
+ }
349
371
 
350
- this.currentServerCapabilities = initializeResult.capabilities;
351
- this.currentServerInfo = initializeResult.serverInfo;
352
- this.currentInstructions = initializeResult.instructions;
353
- messageLayer.sendNotification("notifications/initialized");
354
- this.currentState = "ready";
372
+ this.currentServerCapabilities = structuredClone(initializeResult.capabilities);
373
+ this.currentServerInfo = { ...initializeResult.serverInfo };
374
+ this.currentInstructions = initializeResult.instructions;
375
+ if (onRootsList !== undefined) {
376
+ messageLayer.onRequest("roots/list", async () => ({
377
+ roots: await onRootsList(),
378
+ }));
379
+ }
380
+ messageLayer.sendNotification("notifications/initialized");
381
+ this.currentState = "ready";
382
+
383
+ return initializeResult;
384
+ } catch (error) {
385
+ if (this.transport === transport) {
386
+ const reason = error instanceof Error ? error : new Error(String(error));
387
+ messageLayer.dispose(reason);
388
+ transport.dispose(reason);
389
+ this.messageLayer = null;
390
+ this.transport = null;
391
+ this.currentState = "disconnected";
392
+ }
355
393
 
356
- return initializeResult;
394
+ throw error;
395
+ }
357
396
  }
358
397
 
359
398
  private getServerCapabilitiesOrThrow(): ServerCapabilities {
@@ -372,10 +411,12 @@ export class McpClient {
372
411
  }
373
412
 
374
413
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
375
- return (await messageLayer.sendRequest("tools/list", requestParams)) as {
376
- tools: Tool[];
377
- nextCursor?: string;
378
- };
414
+ const result = await messageLayer.sendRequest("tools/list", requestParams);
415
+ if (!isToolsListResult(result)) {
416
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid tools/list result");
417
+ }
418
+
419
+ return result;
379
420
  }
380
421
 
381
422
  async callTool(params: CallToolParams, options: CallToolOptions = {}): Promise<CallToolResult> {
@@ -385,6 +426,10 @@ export class McpClient {
385
426
  throw new Error("Server does not support tools");
386
427
  }
387
428
 
429
+ if (options.signal?.aborted) {
430
+ throw options.signal.reason;
431
+ }
432
+
388
433
  const requestParams =
389
434
  options.progressToken === undefined
390
435
  ? params
@@ -395,39 +440,63 @@ export class McpClient {
395
440
  },
396
441
  };
397
442
 
398
- let requestId: RequestId | undefined;
399
- const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
400
- onRequestId: (nextRequestId) => {
401
- requestId = nextRequestId;
402
- },
403
- }) as Promise<CallToolResult>;
404
- if (options.signal === undefined) {
405
- return await requestPromise;
443
+ if (options.progressToken !== undefined) {
444
+ this.activeProgressTokens.set(
445
+ options.progressToken,
446
+ (this.activeProgressTokens.get(options.progressToken) ?? 0) + 1
447
+ );
406
448
  }
407
- const signal = options.signal;
408
449
 
409
- let abortListener: (() => void) | undefined;
410
- const abortPromise = new Promise<CallToolResult>((_, reject) => {
411
- const rejectWithAbortReason = () => {
412
- if (requestId !== undefined) {
413
- messageLayer.sendNotification("notifications/cancelled", { requestId });
450
+ try {
451
+ let requestId: RequestId | undefined;
452
+ const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
453
+ onRequestId: (nextRequestId) => {
454
+ requestId = nextRequestId;
455
+ },
456
+ }).then((result) => {
457
+ if (!isCallToolResult(result)) {
458
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid tool result");
414
459
  }
415
- reject(signal.reason);
416
- };
417
460
 
418
- abortListener = rejectWithAbortReason;
419
- signal.addEventListener("abort", abortListener, { once: true });
420
- if (signal.aborted) {
421
- signal.removeEventListener("abort", abortListener);
422
- rejectWithAbortReason();
461
+ return result;
462
+ });
463
+ if (options.signal === undefined) {
464
+ return await requestPromise;
423
465
  }
424
- });
466
+ const signal = options.signal;
425
467
 
426
- try {
427
- return (await Promise.race([requestPromise, abortPromise])) as CallToolResult;
468
+ let abortListener: (() => void) | undefined;
469
+ const abortPromise = new Promise<CallToolResult>((_, reject) => {
470
+ const rejectWithAbortReason = () => {
471
+ if (requestId !== undefined) {
472
+ messageLayer.sendNotification("notifications/cancelled", { requestId });
473
+ }
474
+ reject(signal.reason);
475
+ };
476
+
477
+ abortListener = rejectWithAbortReason;
478
+ signal.addEventListener("abort", abortListener, { once: true });
479
+ if (signal.aborted) {
480
+ signal.removeEventListener("abort", abortListener);
481
+ rejectWithAbortReason();
482
+ }
483
+ });
484
+
485
+ try {
486
+ return (await Promise.race([requestPromise, abortPromise])) as CallToolResult;
487
+ } finally {
488
+ if (abortListener !== undefined) {
489
+ signal.removeEventListener("abort", abortListener);
490
+ }
491
+ }
428
492
  } finally {
429
- if (abortListener !== undefined) {
430
- signal.removeEventListener("abort", abortListener);
493
+ if (options.progressToken !== undefined) {
494
+ const activeCount = this.activeProgressTokens.get(options.progressToken);
495
+ if (activeCount === 1) {
496
+ this.activeProgressTokens.delete(options.progressToken);
497
+ } else if (activeCount !== undefined) {
498
+ this.activeProgressTokens.set(options.progressToken, activeCount - 1);
499
+ }
431
500
  }
432
501
  }
433
502
  }
@@ -484,6 +553,7 @@ export class McpClient {
484
553
  }
485
554
 
486
555
  await messageLayer.sendRequest("resources/subscribe", { uri });
556
+ this.subscribedResourceUris.add(uri);
487
557
  }
488
558
 
489
559
  async unsubscribe(uri: string): Promise<void> {
@@ -494,6 +564,7 @@ export class McpClient {
494
564
  }
495
565
 
496
566
  await messageLayer.sendRequest("resources/unsubscribe", { uri });
567
+ this.subscribedResourceUris.delete(uri);
497
568
  }
498
569
 
499
570
  async listPrompts(params: PaginatedParams = {}): Promise<{ prompts: Prompt[]; nextCursor?: string }> {
@@ -553,6 +624,10 @@ export class McpClient {
553
624
 
554
625
  async sendRootsChanged(): Promise<void> {
555
626
  const messageLayer = this.getMessageLayerOrThrow();
627
+ if (this.currentClientCapabilities?.roots?.listChanged !== true) {
628
+ throw new Error("Client did not advertise roots list changes");
629
+ }
630
+
556
631
  messageLayer.sendNotification("notifications/roots/list_changed");
557
632
  }
558
633
 
@@ -571,6 +646,12 @@ export class McpClient {
571
646
  this.transport?.dispose(closeError);
572
647
  this.messageLayer = null;
573
648
  this.transport = null;
649
+ this.currentServerCapabilities = null;
650
+ this.currentClientCapabilities = null;
651
+ this.currentServerInfo = null;
652
+ this.currentInstructions = undefined;
653
+ this.subscribedResourceUris.clear();
654
+ this.activeProgressTokens.clear();
574
655
  this.currentState = "closed";
575
656
  }
576
657
  }
@@ -2402,7 +2483,6 @@ export class HttpTransport implements McpTransport {
2402
2483
  this.disposed = true;
2403
2484
  this.abortInFlightFetches();
2404
2485
  this.cancelOpenSseReaders();
2405
- this.terminateSession();
2406
2486
 
2407
2487
  if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
2408
2488
  this.writeStream.end();
@@ -2412,9 +2492,24 @@ export class HttpTransport implements McpTransport {
2412
2492
  this.readStream.end();
2413
2493
  }
2414
2494
 
2495
+ void this.closeWithSessionTermination(reason);
2496
+ }
2497
+
2498
+ private async closeWithSessionTermination(reason: Error): Promise<void> {
2499
+ let closeReason = reason;
2500
+ if (this.sessionId !== undefined) {
2501
+ const sessionId = this.sessionId;
2502
+ this.sessionId = undefined;
2503
+ try {
2504
+ await this.sendSessionTerminationRequest(sessionId);
2505
+ } catch (error) {
2506
+ closeReason = error instanceof Error ? error : new Error(String(error));
2507
+ }
2508
+ }
2509
+
2415
2510
  const resolveClosed = this.resolveClosed;
2416
2511
  this.resolveClosed = undefined;
2417
- resolveClosed?.({ reason });
2512
+ resolveClosed?.({ reason: closeReason });
2418
2513
  }
2419
2514
 
2420
2515
  private abortInFlightFetches(): void {
@@ -2469,7 +2564,9 @@ export class HttpTransport implements McpTransport {
2469
2564
  await this.throwForPostHttpError(response);
2470
2565
  this.captureSessionId(response);
2471
2566
  this.maybeOpenGetSseStream();
2472
- await this.forwardResponseMessages(response);
2567
+ void this.forwardResponseMessages(response).catch((error) => {
2568
+ this.dispose(error instanceof Error ? error : new Error(String(error)));
2569
+ });
2473
2570
  }
2474
2571
  }
2475
2572
 
@@ -2479,6 +2576,7 @@ export class HttpTransport implements McpTransport {
2479
2576
  headers.set("Content-Type", "application/json");
2480
2577
  if (this.sessionId !== undefined) {
2481
2578
  headers.set("Mcp-Session-Id", this.sessionId);
2579
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
2482
2580
  }
2483
2581
  return this.authorizeRequestHeaders(headers);
2484
2582
  }
@@ -2488,6 +2586,7 @@ export class HttpTransport implements McpTransport {
2488
2586
  headers.set("Accept", "text/event-stream");
2489
2587
  if (this.sessionId !== undefined) {
2490
2588
  headers.set("Mcp-Session-Id", this.sessionId);
2589
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
2491
2590
  }
2492
2591
  if (this.lastEventId !== undefined) {
2493
2592
  headers.set("Last-Event-ID", this.lastEventId);
@@ -2498,6 +2597,7 @@ export class HttpTransport implements McpTransport {
2498
2597
  private async createDeleteHeaders(sessionId: string): Promise<Headers> {
2499
2598
  const headers = new Headers(this.headers);
2500
2599
  headers.set("Mcp-Session-Id", sessionId);
2600
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
2501
2601
  return this.authorizeRequestHeaders(headers);
2502
2602
  }
2503
2603
 
@@ -2516,6 +2616,10 @@ export class HttpTransport implements McpTransport {
2516
2616
  return;
2517
2617
  }
2518
2618
 
2619
+ if (this.sessionId !== undefined && this.sessionId !== sessionId) {
2620
+ throw new Error("HTTP transport response changed active session ID");
2621
+ }
2622
+
2519
2623
  this.sessionId = sessionId;
2520
2624
  }
2521
2625
 
@@ -2534,26 +2638,22 @@ export class HttpTransport implements McpTransport {
2534
2638
  });
2535
2639
  }
2536
2640
 
2537
- private terminateSession(): void {
2538
- if (this.sessionId === undefined) {
2539
- return;
2540
- }
2541
-
2542
- const sessionId = this.sessionId;
2543
- this.sessionId = undefined;
2544
-
2545
- this.sendSessionTerminationRequest(sessionId).catch(() => undefined);
2546
- }
2547
-
2548
2641
  private async sendSessionTerminationRequest(sessionId: string): Promise<void> {
2549
2642
  const response = await this.fetchImpl(this.url, {
2550
2643
  method: "DELETE",
2551
2644
  headers: await this.createDeleteHeaders(sessionId),
2552
2645
  });
2553
2646
 
2554
- if (response.status === 405) {
2647
+ if (response.status === 405 || response.ok) {
2555
2648
  return;
2556
2649
  }
2650
+
2651
+ const responseBody = (await response.text()).trim();
2652
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
2653
+ const message = responseBody.length === 0
2654
+ ? `HTTP transport DELETE failed (${statusDescriptor})`
2655
+ : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
2656
+ throw new Error(message);
2557
2657
  }
2558
2658
 
2559
2659
  private async consumeGetSseStream(): Promise<void> {
@@ -2566,6 +2666,20 @@ export class HttpTransport implements McpTransport {
2566
2666
  throw new HttpTransportGetSseNotSupportedError();
2567
2667
  }
2568
2668
 
2669
+ if (response.status === 404) {
2670
+ this.sessionId = undefined;
2671
+ throw new Error("HTTP transport session expired (GET 404 response)");
2672
+ }
2673
+
2674
+ if (!response.ok) {
2675
+ const responseBody = (await response.text()).trim();
2676
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
2677
+ const message = responseBody.length === 0
2678
+ ? `HTTP transport GET failed (${statusDescriptor})`
2679
+ : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
2680
+ throw new Error(message);
2681
+ }
2682
+
2569
2683
  const contentType = response.headers.get("Content-Type");
2570
2684
  if (contentType === null) {
2571
2685
  return;
@@ -2577,7 +2691,10 @@ export class HttpTransport implements McpTransport {
2577
2691
  if (!this.disposed && this.sessionId !== undefined && this.lastEventId !== undefined) {
2578
2692
  this.maybeOpenGetSseStream();
2579
2693
  }
2694
+ return;
2580
2695
  }
2696
+
2697
+ return;
2581
2698
  }
2582
2699
 
2583
2700
  private async throwForPostHttpError(response: Response): Promise<void> {
@@ -2645,7 +2762,10 @@ export class HttpTransport implements McpTransport {
2645
2762
 
2646
2763
  if (normalizedContentType.includes("application/json")) {
2647
2764
  await this.forwardJsonResponseMessage(response);
2765
+ return;
2648
2766
  }
2767
+
2768
+ throw new Error("HTTP transport POST returned an unsupported response content type");
2649
2769
  }
2650
2770
 
2651
2771
  private async forwardSseResponseMessages(response: Response): Promise<void> {
@@ -2856,9 +2976,13 @@ function normalizeLine(line: string): string {
2856
2976
 
2857
2977
  export async function* readLines(stream: Readable): AsyncGenerator<string> {
2858
2978
  let buffer = "";
2979
+ const decoder = new TextDecoder();
2859
2980
 
2860
2981
  for await (const chunk of stream as AsyncIterable<unknown>) {
2861
- buffer += chunkToString(chunk);
2982
+ buffer +=
2983
+ chunk instanceof Uint8Array
2984
+ ? decoder.decode(chunk, { stream: true })
2985
+ : decoder.decode() + String(chunk);
2862
2986
 
2863
2987
  while (true) {
2864
2988
  const newlineIndex = buffer.indexOf("\n");
@@ -2872,6 +2996,8 @@ export async function* readLines(stream: Readable): AsyncGenerator<string> {
2872
2996
  }
2873
2997
  }
2874
2998
 
2999
+ buffer += decoder.decode();
3000
+
2875
3001
  if (buffer.length > 0) {
2876
3002
  yield normalizeLine(buffer);
2877
3003
  }
@@ -3383,6 +3509,82 @@ function isObjectRecord(value: unknown): value is Record<string, unknown> {
3383
3509
  return typeof value === "object" && value !== null && !Array.isArray(value);
3384
3510
  }
3385
3511
 
3512
+ function isInitializeResult(value: unknown): value is InitializeResult {
3513
+ if (!isObjectRecord(value) || typeof value.protocolVersion !== "string") {
3514
+ return false;
3515
+ }
3516
+
3517
+ if (!isServerCapabilities(value.capabilities)) {
3518
+ return false;
3519
+ }
3520
+
3521
+ if (
3522
+ !isObjectRecord(value.serverInfo)
3523
+ || typeof value.serverInfo.name !== "string"
3524
+ || value.serverInfo.name.length === 0
3525
+ || typeof value.serverInfo.version !== "string"
3526
+ || value.serverInfo.version.length === 0
3527
+ ) {
3528
+ return false;
3529
+ }
3530
+
3531
+ return value.instructions === undefined || typeof value.instructions === "string";
3532
+ }
3533
+
3534
+ function isServerCapabilities(value: unknown): value is ServerCapabilities {
3535
+ if (!isObjectRecord(value)) {
3536
+ return false;
3537
+ }
3538
+
3539
+ for (const capability of ["prompts", "resources", "tools", "logging", "completions", "experimental"] as const) {
3540
+ if (value[capability] !== undefined && !isObjectRecord(value[capability])) {
3541
+ return false;
3542
+ }
3543
+ }
3544
+
3545
+ return true;
3546
+ }
3547
+
3548
+ function isCallToolResult(value: unknown): value is CallToolResult {
3549
+ if (!isObjectRecord(value) || !Array.isArray(value.content)) {
3550
+ return false;
3551
+ }
3552
+
3553
+ if (value.isError !== undefined && typeof value.isError !== "boolean") {
3554
+ return false;
3555
+ }
3556
+
3557
+ return value.content.every(isContentItem);
3558
+ }
3559
+
3560
+ function isToolsListResult(value: unknown): value is { tools: Tool[]; nextCursor?: string } {
3561
+ return isObjectRecord(value)
3562
+ && Array.isArray(value.tools)
3563
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
3564
+ }
3565
+
3566
+ function isContentItem(value: unknown): value is ContentItem {
3567
+ if (!isObjectRecord(value)) {
3568
+ return false;
3569
+ }
3570
+
3571
+ if (value.type === "text") {
3572
+ return typeof value.text === "string";
3573
+ }
3574
+
3575
+ if (value.type === "image" || value.type === "audio") {
3576
+ return typeof value.data === "string" && typeof value.mimeType === "string";
3577
+ }
3578
+
3579
+ if (value.type !== "resource" || !isObjectRecord(value.resource)) {
3580
+ return false;
3581
+ }
3582
+
3583
+ return typeof value.resource.uri === "string"
3584
+ && (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
3585
+ && (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
3586
+ }
3587
+
3386
3588
  function hasOwn(
3387
3589
  value: Record<string, unknown>,
3388
3590
  property: string
@@ -17,7 +17,7 @@ describe("McpClient integration tools with tiny-stdio-mcp-test-server", () => {
17
17
  try {
18
18
  expect(client.serverInfo).toEqual({
19
19
  name: "tiny-stdio-mcp-test-server",
20
- version: "0.0.1",
20
+ version: "0.1.0",
21
21
  });
22
22
 
23
23
  const { tools } = await client.listTools();
@@ -4,8 +4,8 @@ import type {
4
4
  OAuthMetadataFetch,
5
5
  OAuthProtectedResourceMetadata,
6
6
  OAuthUnauthorizedChallenge,
7
- } from "../../mcp-oauth/dist/index.js";
8
- import { canonicalizeResourceIndicator } from "../../mcp-oauth/dist/index.js";
7
+ } from "mcp-oauth";
8
+ import { canonicalizeResourceIndicator } from "mcp-oauth";
9
9
 
10
10
  export type {
11
11
  OAuthAuthorizationServerMetadata,
@@ -237,11 +237,7 @@ export class OAuthMetadataDiscovery {
237
237
  resourceMetadataUrl
238
238
  );
239
239
  const memoryCachedResult = this.memoryCache.get(cacheKey);
240
- if (
241
- memoryCachedResult !== undefined &&
242
- (resourceMetadataUrl === undefined
243
- || memoryCachedResult.resourceMetadataUrl === resourceMetadataLocation)
244
- ) {
240
+ if (memoryCachedResult !== undefined && resourceMetadataUrl === undefined) {
245
241
  return memoryCachedResult;
246
242
  }
247
243
 
@@ -249,8 +245,7 @@ export class OAuthMetadataDiscovery {
249
245
  if (
250
246
  sharedCachedResult !== null &&
251
247
  sharedCachedResult !== undefined &&
252
- (resourceMetadataUrl === undefined
253
- || sharedCachedResult.resourceMetadataUrl === resourceMetadataLocation)
248
+ resourceMetadataUrl === undefined
254
249
  ) {
255
250
  this.memoryCache.set(cacheKey, sharedCachedResult);
256
251
  return sharedCachedResult;
@@ -530,7 +525,7 @@ export function parseBearerWwwAuthenticateHeader(
530
525
  }
531
526
 
532
527
  index = skipOptionalWhitespace(headerValue, scheme.nextIndex);
533
- const params: Record<string, string> = {};
528
+ const params: Record<string, string> = Object.create(null) as Record<string, string>;
534
529
 
535
530
  if (index < headerValue.length && headerValue[index] !== ",") {
536
531
  const token68 = readToken68(headerValue, index);