toolcraft 0.0.23 → 0.0.25

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 (152) 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 +50 -13
  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/args.d.ts +1 -0
  86. package/node_modules/@poe-code/process-runner/dist/docker/args.js +11 -3
  87. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +377 -130
  88. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +78 -10
  89. package/node_modules/@poe-code/process-runner/dist/docker/env-file.d.ts +6 -0
  90. package/node_modules/@poe-code/process-runner/dist/docker/env-file.js +49 -0
  91. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  92. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +21 -5
  93. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  94. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  95. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  96. package/node_modules/@poe-code/process-runner/dist/types.d.ts +6 -0
  97. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +61 -0
  98. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +503 -0
  99. package/node_modules/@poe-code/process-runner/package.json +1 -1
  100. package/node_modules/@poe-code/task-list/README.md +0 -2
  101. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  102. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  103. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  104. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  105. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  106. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  107. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  108. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  109. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  110. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  111. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  112. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  113. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  114. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  115. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  116. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  117. package/node_modules/@poe-code/task-list/package.json +1 -2
  118. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  119. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +8 -0
  120. package/node_modules/auth-store/dist/encrypted-file-store.js +104 -8
  121. package/node_modules/auth-store/dist/index.d.ts +1 -1
  122. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  123. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  124. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  125. package/node_modules/auth-store/dist/provider-store.js +55 -7
  126. package/node_modules/auth-store/dist/types.d.ts +3 -1
  127. package/node_modules/auth-store/package.json +2 -1
  128. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  129. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  130. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  131. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  132. package/node_modules/mcp-oauth/package.json +1 -0
  133. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  134. package/node_modules/tiny-mcp-client/dist/internal.d.ts +9 -4
  135. package/node_modules/tiny-mcp-client/dist/internal.js +244 -66
  136. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  137. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  138. package/node_modules/tiny-mcp-client/package.json +2 -1
  139. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  140. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  141. package/node_modules/tiny-mcp-client/src/internal.ts +287 -76
  142. package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +32 -0
  143. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  144. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  145. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  146. package/package.json +10 -12
  147. package/node_modules/@poe-code/file-lock/README.md +0 -52
  148. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  149. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  150. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  151. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  152. package/node_modules/@poe-code/file-lock/package.json +0 -23
@@ -1,15 +1,18 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { PassThrough } from "node:stream";
3
- import { createOAuthClientProvider, OAuthError, } from "../../mcp-oauth/dist/index.js";
3
+ import { createOAuthClientProvider, OAuthError, } from "mcp-oauth";
4
4
  import { OAuthMetadataDiscovery, parseBearerWwwAuthenticateHeader, } from "./oauth-discovery.js";
5
5
  export { OAuthMetadataDiscovery, discoverOAuthMetadata, parseBearerWwwAuthenticateHeader, resolveAuthorizationServerMetadataUrl, resolveProtectedResourceMetadataUrl, } from "./oauth-discovery.js";
6
- export { createAuthStoreSessionStore, createDefaultOAuthClientProvider, } from "../../mcp-oauth/dist/index.js";
6
+ export { createAuthStoreSessionStore, createDefaultOAuthClientProvider, } from "mcp-oauth";
7
7
  const MCP_PROTOCOL_VERSION = "2025-03-26";
8
8
  export class McpClient {
9
9
  currentState = "disconnected";
10
10
  currentServerCapabilities = null;
11
+ currentClientCapabilities = null;
11
12
  currentServerInfo = null;
12
13
  currentInstructions;
14
+ subscribedResourceUris = new Set();
15
+ activeProgressTokens = new Map();
13
16
  options;
14
17
  transport = null;
15
18
  messageLayer = null;
@@ -20,7 +23,9 @@ export class McpClient {
20
23
  return this.currentState;
21
24
  }
22
25
  get serverCapabilities() {
23
- return this.currentServerCapabilities;
26
+ return this.currentServerCapabilities === null
27
+ ? null
28
+ : structuredClone(this.currentServerCapabilities);
24
29
  }
25
30
  get serverInfo() {
26
31
  return this.currentServerInfo;
@@ -44,22 +49,23 @@ export class McpClient {
44
49
  if (this.currentState !== "disconnected" && this.currentState !== "closed") {
45
50
  throw new Error("MCP client is already connected");
46
51
  }
52
+ this.currentServerCapabilities = null;
53
+ this.currentClientCapabilities = null;
54
+ this.currentServerInfo = null;
55
+ this.currentInstructions = undefined;
56
+ this.subscribedResourceUris.clear();
57
+ this.activeProgressTokens.clear();
47
58
  const transportClosedReason = transport.closed
48
59
  .then((closedEvent) => closedEvent.reason)
49
60
  .catch((error) => error instanceof Error ? error : new Error(String(error)));
50
- const messageLayer = new JsonRpcMessageLayer(transport.readable, transport.writable, 30_000, transportClosedReason);
61
+ const messageLayer = new JsonRpcMessageLayer(transport.readable, transport.writable, this.options.requestTimeoutMs, transportClosedReason);
51
62
  const { onSamplingRequest, onRootsList, onToolsChanged, onResourcesChanged, onResourceUpdated, onPromptsChanged, onLog, onProgress, } = this.options;
52
63
  messageLayer.onRequest("ping", () => ({}));
53
64
  if (onSamplingRequest !== undefined) {
54
65
  messageLayer.onRequest("sampling/createMessage", (params) => onSamplingRequest(params));
55
66
  }
56
- if (onRootsList !== undefined) {
57
- messageLayer.onRequest("roots/list", async () => ({
58
- roots: await onRootsList(),
59
- }));
60
- }
61
67
  messageLayer.onNotification("notifications/tools/list_changed", async () => {
62
- if (onToolsChanged === undefined) {
68
+ if (onToolsChanged === undefined || this.currentServerCapabilities?.tools?.listChanged !== true) {
63
69
  return;
64
70
  }
65
71
  await onToolsChanged();
@@ -78,7 +84,7 @@ export class McpClient {
78
84
  return;
79
85
  }
80
86
  const { uri } = params;
81
- if (typeof uri !== "string") {
87
+ if (typeof uri !== "string" || !this.subscribedResourceUris.has(uri)) {
82
88
  return;
83
89
  }
84
90
  await onResourceUpdated(uri);
@@ -113,7 +119,9 @@ export class McpClient {
113
119
  return;
114
120
  }
115
121
  const { progressToken, progress } = params;
116
- if (!isRequestId(progressToken) || typeof progress !== "number") {
122
+ if (!isRequestId(progressToken) ||
123
+ typeof progress !== "number" ||
124
+ !this.activeProgressTokens.has(progressToken)) {
117
125
  return;
118
126
  }
119
127
  const progressParams = {
@@ -138,6 +146,8 @@ export class McpClient {
138
146
  this.transport = transport;
139
147
  this.messageLayer = messageLayer;
140
148
  this.currentState = "initializing";
149
+ this.subscribedResourceUris.clear();
150
+ this.activeProgressTokens.clear();
141
151
  transport.closed
142
152
  .then((closedEvent) => {
143
153
  if (this.transport !== transport) {
@@ -169,20 +179,43 @@ export class McpClient {
169
179
  ...(capabilities.roots ?? {}),
170
180
  };
171
181
  }
172
- const initializeResult = (await messageLayer.sendRequest("initialize", {
173
- protocolVersion: MCP_PROTOCOL_VERSION,
174
- clientInfo: this.options.clientInfo,
175
- capabilities,
176
- }));
177
- if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
178
- throw new McpError(ERROR_INVALID_REQUEST, `Unsupported protocol version: ${initializeResult.protocolVersion}`);
179
- }
180
- this.currentServerCapabilities = initializeResult.capabilities;
181
- this.currentServerInfo = initializeResult.serverInfo;
182
- this.currentInstructions = initializeResult.instructions;
183
- messageLayer.sendNotification("notifications/initialized");
184
- this.currentState = "ready";
185
- return initializeResult;
182
+ this.currentClientCapabilities = structuredClone(capabilities);
183
+ try {
184
+ const initializeResultValue = await messageLayer.sendRequest("initialize", {
185
+ protocolVersion: MCP_PROTOCOL_VERSION,
186
+ clientInfo: this.options.clientInfo,
187
+ capabilities,
188
+ });
189
+ if (!isInitializeResult(initializeResultValue)) {
190
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid initialize result");
191
+ }
192
+ const initializeResult = initializeResultValue;
193
+ if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
194
+ throw new McpError(ERROR_INVALID_REQUEST, `Unsupported protocol version: ${initializeResult.protocolVersion}`);
195
+ }
196
+ this.currentServerCapabilities = structuredClone(initializeResult.capabilities);
197
+ this.currentServerInfo = { ...initializeResult.serverInfo };
198
+ this.currentInstructions = initializeResult.instructions;
199
+ if (onRootsList !== undefined) {
200
+ messageLayer.onRequest("roots/list", async () => ({
201
+ roots: await onRootsList(),
202
+ }));
203
+ }
204
+ messageLayer.sendNotification("notifications/initialized");
205
+ this.currentState = "ready";
206
+ return initializeResult;
207
+ }
208
+ catch (error) {
209
+ if (this.transport === transport) {
210
+ const reason = error instanceof Error ? error : new Error(String(error));
211
+ messageLayer.dispose(reason);
212
+ transport.dispose(reason);
213
+ this.messageLayer = null;
214
+ this.transport = null;
215
+ this.currentState = "disconnected";
216
+ }
217
+ throw error;
218
+ }
186
219
  }
187
220
  getServerCapabilitiesOrThrow() {
188
221
  if (this.currentServerCapabilities === null) {
@@ -197,7 +230,11 @@ export class McpClient {
197
230
  throw new Error("Server does not support tools");
198
231
  }
199
232
  const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
200
- return (await messageLayer.sendRequest("tools/list", requestParams));
233
+ const result = await messageLayer.sendRequest("tools/list", requestParams);
234
+ if (!isToolsListResult(result)) {
235
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid tools/list result");
236
+ }
237
+ return result;
201
238
  }
202
239
  async callTool(params, options = {}) {
203
240
  const messageLayer = this.getMessageLayerOrThrow();
@@ -205,6 +242,9 @@ export class McpClient {
205
242
  if (serverCapabilities.tools === undefined) {
206
243
  throw new Error("Server does not support tools");
207
244
  }
245
+ if (options.signal?.aborted) {
246
+ throw options.signal.reason;
247
+ }
208
248
  const requestParams = options.progressToken === undefined
209
249
  ? params
210
250
  : {
@@ -213,37 +253,65 @@ export class McpClient {
213
253
  progressToken: options.progressToken,
214
254
  },
215
255
  };
216
- let requestId;
217
- const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
218
- onRequestId: (nextRequestId) => {
219
- requestId = nextRequestId;
220
- },
221
- });
222
- if (options.signal === undefined) {
223
- return await requestPromise;
224
- }
225
- const signal = options.signal;
226
- let abortListener;
227
- const abortPromise = new Promise((_, reject) => {
228
- const rejectWithAbortReason = () => {
229
- if (requestId !== undefined) {
230
- messageLayer.sendNotification("notifications/cancelled", { requestId });
256
+ if (options.progressToken !== undefined) {
257
+ this.activeProgressTokens.set(options.progressToken, (this.activeProgressTokens.get(options.progressToken) ?? 0) + 1);
258
+ }
259
+ try {
260
+ let requestId;
261
+ let cancellationSent = false;
262
+ const sendCancellationNotification = () => {
263
+ if (requestId === undefined || cancellationSent) {
264
+ return;
231
265
  }
232
- reject(signal.reason);
266
+ cancellationSent = true;
267
+ messageLayer.sendNotification("notifications/cancelled", { requestId });
233
268
  };
234
- abortListener = rejectWithAbortReason;
235
- signal.addEventListener("abort", abortListener, { once: true });
236
- if (signal.aborted) {
237
- signal.removeEventListener("abort", abortListener);
238
- rejectWithAbortReason();
269
+ const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
270
+ onRequestId: (nextRequestId) => {
271
+ requestId = nextRequestId;
272
+ },
273
+ onTimeout: sendCancellationNotification,
274
+ }).then((result) => {
275
+ if (!isCallToolResult(result)) {
276
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid tool result");
277
+ }
278
+ return result;
279
+ });
280
+ if (options.signal === undefined) {
281
+ return await requestPromise;
282
+ }
283
+ const signal = options.signal;
284
+ let abortListener;
285
+ const abortPromise = new Promise((_, reject) => {
286
+ const rejectWithAbortReason = () => {
287
+ sendCancellationNotification();
288
+ reject(signal.reason);
289
+ };
290
+ abortListener = rejectWithAbortReason;
291
+ signal.addEventListener("abort", abortListener, { once: true });
292
+ if (signal.aborted) {
293
+ signal.removeEventListener("abort", abortListener);
294
+ rejectWithAbortReason();
295
+ }
296
+ });
297
+ try {
298
+ return (await Promise.race([requestPromise, abortPromise]));
299
+ }
300
+ finally {
301
+ if (abortListener !== undefined) {
302
+ signal.removeEventListener("abort", abortListener);
303
+ }
239
304
  }
240
- });
241
- try {
242
- return (await Promise.race([requestPromise, abortPromise]));
243
305
  }
244
306
  finally {
245
- if (abortListener !== undefined) {
246
- signal.removeEventListener("abort", abortListener);
307
+ if (options.progressToken !== undefined) {
308
+ const activeCount = this.activeProgressTokens.get(options.progressToken);
309
+ if (activeCount === 1) {
310
+ this.activeProgressTokens.delete(options.progressToken);
311
+ }
312
+ else if (activeCount !== undefined) {
313
+ this.activeProgressTokens.set(options.progressToken, activeCount - 1);
314
+ }
247
315
  }
248
316
  }
249
317
  }
@@ -280,6 +348,7 @@ export class McpClient {
280
348
  throw new Error("Server does not support resource subscriptions");
281
349
  }
282
350
  await messageLayer.sendRequest("resources/subscribe", { uri });
351
+ this.subscribedResourceUris.add(uri);
283
352
  }
284
353
  async unsubscribe(uri) {
285
354
  const messageLayer = this.getMessageLayerOrThrow();
@@ -288,6 +357,7 @@ export class McpClient {
288
357
  throw new Error("Server does not support resource subscriptions");
289
358
  }
290
359
  await messageLayer.sendRequest("resources/unsubscribe", { uri });
360
+ this.subscribedResourceUris.delete(uri);
291
361
  }
292
362
  async listPrompts(params = {}) {
293
363
  const messageLayer = this.getMessageLayerOrThrow();
@@ -332,6 +402,9 @@ export class McpClient {
332
402
  }
333
403
  async sendRootsChanged() {
334
404
  const messageLayer = this.getMessageLayerOrThrow();
405
+ if (this.currentClientCapabilities?.roots?.listChanged !== true) {
406
+ throw new Error("Client did not advertise roots list changes");
407
+ }
335
408
  messageLayer.sendNotification("notifications/roots/list_changed");
336
409
  }
337
410
  async ping() {
@@ -347,6 +420,12 @@ export class McpClient {
347
420
  this.transport?.dispose(closeError);
348
421
  this.messageLayer = null;
349
422
  this.transport = null;
423
+ this.currentServerCapabilities = null;
424
+ this.currentClientCapabilities = null;
425
+ this.currentServerInfo = null;
426
+ this.currentInstructions = undefined;
427
+ this.subscribedResourceUris.clear();
428
+ this.activeProgressTokens.clear();
350
429
  this.currentState = "closed";
351
430
  }
352
431
  }
@@ -1543,16 +1622,29 @@ export class HttpTransport {
1543
1622
  this.disposed = true;
1544
1623
  this.abortInFlightFetches();
1545
1624
  this.cancelOpenSseReaders();
1546
- this.terminateSession();
1547
1625
  if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
1548
1626
  this.writeStream.end();
1549
1627
  }
1550
1628
  if (!this.readStream.destroyed && !this.readStream.writableEnded) {
1551
1629
  this.readStream.end();
1552
1630
  }
1631
+ void this.closeWithSessionTermination(reason);
1632
+ }
1633
+ async closeWithSessionTermination(reason) {
1634
+ let closeReason = reason;
1635
+ if (this.sessionId !== undefined) {
1636
+ const sessionId = this.sessionId;
1637
+ this.sessionId = undefined;
1638
+ try {
1639
+ await this.sendSessionTerminationRequest(sessionId);
1640
+ }
1641
+ catch (error) {
1642
+ closeReason = error instanceof Error ? error : new Error(String(error));
1643
+ }
1644
+ }
1553
1645
  const resolveClosed = this.resolveClosed;
1554
1646
  this.resolveClosed = undefined;
1555
- resolveClosed?.({ reason });
1647
+ resolveClosed?.({ reason: closeReason });
1556
1648
  }
1557
1649
  abortInFlightFetches() {
1558
1650
  for (const abortController of this.inFlightFetchAbortControllers) {
@@ -1598,7 +1690,9 @@ export class HttpTransport {
1598
1690
  await this.throwForPostHttpError(response);
1599
1691
  this.captureSessionId(response);
1600
1692
  this.maybeOpenGetSseStream();
1601
- await this.forwardResponseMessages(response);
1693
+ void this.forwardResponseMessages(response).catch((error) => {
1694
+ this.dispose(error instanceof Error ? error : new Error(String(error)));
1695
+ });
1602
1696
  }
1603
1697
  }
1604
1698
  async createPostHeaders() {
@@ -1607,6 +1701,7 @@ export class HttpTransport {
1607
1701
  headers.set("Content-Type", "application/json");
1608
1702
  if (this.sessionId !== undefined) {
1609
1703
  headers.set("Mcp-Session-Id", this.sessionId);
1704
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1610
1705
  }
1611
1706
  return this.authorizeRequestHeaders(headers);
1612
1707
  }
@@ -1615,6 +1710,7 @@ export class HttpTransport {
1615
1710
  headers.set("Accept", "text/event-stream");
1616
1711
  if (this.sessionId !== undefined) {
1617
1712
  headers.set("Mcp-Session-Id", this.sessionId);
1713
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1618
1714
  }
1619
1715
  if (this.lastEventId !== undefined) {
1620
1716
  headers.set("Last-Event-ID", this.lastEventId);
@@ -1624,6 +1720,7 @@ export class HttpTransport {
1624
1720
  async createDeleteHeaders(sessionId) {
1625
1721
  const headers = new Headers(this.headers);
1626
1722
  headers.set("Mcp-Session-Id", sessionId);
1723
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1627
1724
  return this.authorizeRequestHeaders(headers);
1628
1725
  }
1629
1726
  async authorizeRequestHeaders(headers) {
@@ -1639,6 +1736,9 @@ export class HttpTransport {
1639
1736
  if (sessionId === null || sessionId.length === 0) {
1640
1737
  return;
1641
1738
  }
1739
+ if (this.sessionId !== undefined && this.sessionId !== sessionId) {
1740
+ throw new Error("HTTP transport response changed active session ID");
1741
+ }
1642
1742
  this.sessionId = sessionId;
1643
1743
  }
1644
1744
  maybeOpenGetSseStream() {
@@ -1653,22 +1753,20 @@ export class HttpTransport {
1653
1753
  this.dispose(error instanceof Error ? error : new Error(String(error)));
1654
1754
  });
1655
1755
  }
1656
- terminateSession() {
1657
- if (this.sessionId === undefined) {
1658
- return;
1659
- }
1660
- const sessionId = this.sessionId;
1661
- this.sessionId = undefined;
1662
- this.sendSessionTerminationRequest(sessionId).catch(() => undefined);
1663
- }
1664
1756
  async sendSessionTerminationRequest(sessionId) {
1665
1757
  const response = await this.fetchImpl(this.url, {
1666
1758
  method: "DELETE",
1667
1759
  headers: await this.createDeleteHeaders(sessionId),
1668
1760
  });
1669
- if (response.status === 405) {
1761
+ if (response.status === 405 || response.ok) {
1670
1762
  return;
1671
1763
  }
1764
+ const responseBody = (await response.text()).trim();
1765
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
1766
+ const message = responseBody.length === 0
1767
+ ? `HTTP transport DELETE failed (${statusDescriptor})`
1768
+ : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
1769
+ throw new Error(message);
1672
1770
  }
1673
1771
  async consumeGetSseStream() {
1674
1772
  const response = await this.fetchWithOAuthRetry({
@@ -1678,6 +1776,18 @@ export class HttpTransport {
1678
1776
  if (response.status === 405) {
1679
1777
  throw new HttpTransportGetSseNotSupportedError();
1680
1778
  }
1779
+ if (response.status === 404) {
1780
+ this.sessionId = undefined;
1781
+ throw new Error("HTTP transport session expired (GET 404 response)");
1782
+ }
1783
+ if (!response.ok) {
1784
+ const responseBody = (await response.text()).trim();
1785
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
1786
+ const message = responseBody.length === 0
1787
+ ? `HTTP transport GET failed (${statusDescriptor})`
1788
+ : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
1789
+ throw new Error(message);
1790
+ }
1681
1791
  const contentType = response.headers.get("Content-Type");
1682
1792
  if (contentType === null) {
1683
1793
  return;
@@ -1688,7 +1798,9 @@ export class HttpTransport {
1688
1798
  if (!this.disposed && this.sessionId !== undefined && this.lastEventId !== undefined) {
1689
1799
  this.maybeOpenGetSseStream();
1690
1800
  }
1801
+ return;
1691
1802
  }
1803
+ return;
1692
1804
  }
1693
1805
  async throwForPostHttpError(response) {
1694
1806
  if (response.status < 400) {
@@ -1744,7 +1856,9 @@ export class HttpTransport {
1744
1856
  }
1745
1857
  if (normalizedContentType.includes("application/json")) {
1746
1858
  await this.forwardJsonResponseMessage(response);
1859
+ return;
1747
1860
  }
1861
+ throw new Error("HTTP transport POST returned an unsupported response content type");
1748
1862
  }
1749
1863
  async forwardSseResponseMessages(response) {
1750
1864
  if (response.body === null) {
@@ -1866,8 +1980,12 @@ function normalizeLine(line) {
1866
1980
  }
1867
1981
  export async function* readLines(stream) {
1868
1982
  let buffer = "";
1983
+ const decoder = new TextDecoder();
1869
1984
  for await (const chunk of stream) {
1870
- buffer += chunkToString(chunk);
1985
+ buffer +=
1986
+ chunk instanceof Uint8Array
1987
+ ? decoder.decode(chunk, { stream: true })
1988
+ : decoder.decode() + String(chunk);
1871
1989
  while (true) {
1872
1990
  const newlineIndex = buffer.indexOf("\n");
1873
1991
  if (newlineIndex === -1) {
@@ -1878,6 +1996,7 @@ export async function* readLines(stream) {
1878
1996
  yield normalizeLine(line);
1879
1997
  }
1880
1998
  }
1999
+ buffer += decoder.decode();
1881
2000
  if (buffer.length > 0) {
1882
2001
  yield normalizeLine(buffer);
1883
2002
  }
@@ -2035,6 +2154,7 @@ export class JsonRpcMessageLayer {
2035
2154
  return new Promise((resolve, reject) => {
2036
2155
  const timeout = setTimeout(() => {
2037
2156
  this.pendingRequests.delete(id);
2157
+ options.onTimeout?.(id);
2038
2158
  reject(new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`));
2039
2159
  }, timeoutMs);
2040
2160
  this.pendingRequests.set(id, { resolve, reject, timeout });
@@ -2253,6 +2373,64 @@ export class JsonRpcMessageLayer {
2253
2373
  function isObjectRecord(value) {
2254
2374
  return typeof value === "object" && value !== null && !Array.isArray(value);
2255
2375
  }
2376
+ function isInitializeResult(value) {
2377
+ if (!isObjectRecord(value) || typeof value.protocolVersion !== "string") {
2378
+ return false;
2379
+ }
2380
+ if (!isServerCapabilities(value.capabilities)) {
2381
+ return false;
2382
+ }
2383
+ if (!isObjectRecord(value.serverInfo)
2384
+ || typeof value.serverInfo.name !== "string"
2385
+ || value.serverInfo.name.length === 0
2386
+ || typeof value.serverInfo.version !== "string"
2387
+ || value.serverInfo.version.length === 0) {
2388
+ return false;
2389
+ }
2390
+ return value.instructions === undefined || typeof value.instructions === "string";
2391
+ }
2392
+ function isServerCapabilities(value) {
2393
+ if (!isObjectRecord(value)) {
2394
+ return false;
2395
+ }
2396
+ for (const capability of ["prompts", "resources", "tools", "logging", "completions", "experimental"]) {
2397
+ if (value[capability] !== undefined && !isObjectRecord(value[capability])) {
2398
+ return false;
2399
+ }
2400
+ }
2401
+ return true;
2402
+ }
2403
+ function isCallToolResult(value) {
2404
+ if (!isObjectRecord(value) || !Array.isArray(value.content)) {
2405
+ return false;
2406
+ }
2407
+ if (value.isError !== undefined && typeof value.isError !== "boolean") {
2408
+ return false;
2409
+ }
2410
+ return value.content.every(isContentItem);
2411
+ }
2412
+ function isToolsListResult(value) {
2413
+ return isObjectRecord(value)
2414
+ && Array.isArray(value.tools)
2415
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
2416
+ }
2417
+ function isContentItem(value) {
2418
+ if (!isObjectRecord(value)) {
2419
+ return false;
2420
+ }
2421
+ if (value.type === "text") {
2422
+ return typeof value.text === "string";
2423
+ }
2424
+ if (value.type === "image" || value.type === "audio") {
2425
+ return typeof value.data === "string" && typeof value.mimeType === "string";
2426
+ }
2427
+ if (value.type !== "resource" || !isObjectRecord(value.resource)) {
2428
+ return false;
2429
+ }
2430
+ return typeof value.resource.uri === "string"
2431
+ && (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
2432
+ && (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
2433
+ }
2256
2434
  function hasOwn(value, property) {
2257
2435
  return Object.prototype.hasOwnProperty.call(value, property);
2258
2436
  }
@@ -1,4 +1,4 @@
1
- import type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge } from "../../mcp-oauth/dist/index.js";
1
+ import type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge } from "mcp-oauth";
2
2
  export type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge, };
3
3
  export interface OAuthDiscoveryCache {
4
4
  get(resourceUrl: string): OAuthDiscoveryResult | null | undefined | Promise<OAuthDiscoveryResult | null | undefined>;
@@ -1,4 +1,4 @@
1
- import { canonicalizeResourceIndicator } from "../../mcp-oauth/dist/index.js";
1
+ import { canonicalizeResourceIndicator } from "mcp-oauth";
2
2
  function defaultOAuthMetadataFetch(input, init) {
3
3
  return fetch(input, init);
4
4
  }
@@ -129,16 +129,13 @@ export class OAuthMetadataDiscovery {
129
129
  const cacheKey = canonicalizeResourceIndicator(resourceUrl);
130
130
  const resourceMetadataLocation = resolveProtectedResourceMetadataUrl(cacheKey, resourceMetadataUrl);
131
131
  const memoryCachedResult = this.memoryCache.get(cacheKey);
132
- if (memoryCachedResult !== undefined &&
133
- (resourceMetadataUrl === undefined
134
- || memoryCachedResult.resourceMetadataUrl === resourceMetadataLocation)) {
132
+ if (memoryCachedResult !== undefined && resourceMetadataUrl === undefined) {
135
133
  return memoryCachedResult;
136
134
  }
137
135
  const sharedCachedResult = await this.cache?.get(cacheKey);
138
136
  if (sharedCachedResult !== null &&
139
137
  sharedCachedResult !== undefined &&
140
- (resourceMetadataUrl === undefined
141
- || sharedCachedResult.resourceMetadataUrl === resourceMetadataLocation)) {
138
+ resourceMetadataUrl === undefined) {
142
139
  this.memoryCache.set(cacheKey, sharedCachedResult);
143
140
  return sharedCachedResult;
144
141
  }
@@ -340,7 +337,7 @@ export function parseBearerWwwAuthenticateHeader(headerValue) {
340
337
  break;
341
338
  }
342
339
  index = skipOptionalWhitespace(headerValue, scheme.nextIndex);
343
- const params = {};
340
+ const params = Object.create(null);
344
341
  if (index < headerValue.length && headerValue[index] !== ",") {
345
342
  const token68 = readToken68(headerValue, index);
346
343
  if (token68 !== null) {
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "tiny-mcp-client",
3
3
  "version": "0.1.0",
4
+ "private": true,
4
5
  "type": "module",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
7
8
  "scripts": {
8
- "build": "tsc"
9
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc"
9
10
  },
10
11
  "repository": {
11
12
  "type": "git",
@@ -4,7 +4,7 @@ import {
4
4
  OAuthError,
5
5
  type OAuthSessionStore,
6
6
  type StoredOAuthSession,
7
- } from "../../mcp-oauth/dist/index.js";
7
+ } from "mcp-oauth";
8
8
  import { nodeFetch } from "tiny-http-mcp-server/testing";
9
9
  import {
10
10
  createMcpOAuthTestServer,
@@ -427,6 +427,45 @@ describe("discoverOAuthMetadata", () => {
427
427
  hintedAuthorizationServerMetadataUrl,
428
428
  ]);
429
429
  });
430
+
431
+ it("refetches an explicitly repeated resource_metadata hint", async () => {
432
+ const resourceUrl = "https://resource.example.com/tenant/mcp";
433
+ const resourceMetadataUrl =
434
+ "https://resource.example.com/.well-known/oauth-protected-resource/tenant/mcp";
435
+ const firstIssuer = "https://auth.example.com/issuer-a";
436
+ const secondIssuer = "https://auth.example.com/issuer-b";
437
+ let rotated = false;
438
+ const fetchMock = vi.fn(async (input: string | URL): Promise<Response> => {
439
+ const url = input.toString();
440
+ if (url === resourceMetadataUrl) {
441
+ return jsonResponse({
442
+ resource: resourceUrl,
443
+ authorization_servers: [rotated ? secondIssuer : firstIssuer],
444
+ });
445
+ }
446
+ const issuer = url.includes("issuer-a") ? firstIssuer : secondIssuer;
447
+ return jsonResponse({
448
+ issuer,
449
+ authorization_endpoint: `${issuer}/authorize`,
450
+ token_endpoint: `${issuer}/token`,
451
+ response_types_supported: ["code"],
452
+ code_challenge_methods_supported: ["S256"],
453
+ });
454
+ });
455
+ const discoveryClient = new OAuthMetadataDiscovery({ fetch: fetchMock });
456
+
457
+ expect((await discoveryClient.discover(resourceUrl)).authorizationServer).toBe(firstIssuer);
458
+ rotated = true;
459
+ expect(
460
+ (await discoveryClient.discover(resourceUrl, { resourceMetadataUrl })).authorizationServer
461
+ ).toBe(secondIssuer);
462
+ expect(fetchMock.mock.calls.map(([input]) => input.toString())).toEqual([
463
+ resourceMetadataUrl,
464
+ "https://auth.example.com/.well-known/oauth-authorization-server/issuer-a",
465
+ resourceMetadataUrl,
466
+ "https://auth.example.com/.well-known/oauth-authorization-server/issuer-b",
467
+ ]);
468
+ });
430
469
  });
431
470
 
432
471
  describe("parseBearerWwwAuthenticateHeader", () => {
@@ -464,6 +503,13 @@ describe("parseBearerWwwAuthenticateHeader", () => {
464
503
  'Digest abc123==, Bearer abc123==, Bearer realm="mcp", resource_metadata="https://resource.example.com/.well-known/oauth-protected-resource/mcp"',
465
504
  });
466
505
  });
506
+
507
+ it("preserves a __proto__ auth parameter as parsed data", () => {
508
+ const challenge = parseBearerWwwAuthenticateHeader('Bearer __proto__="visible"');
509
+
510
+ expect(Object.hasOwn(challenge!.params, "__proto__")).toBe(true);
511
+ expect(challenge?.params.__proto__).toBe("visible");
512
+ });
467
513
  });
468
514
 
469
515
  describe("resolveAuthorizationServerMetadataUrl", () => {