toolcraft 0.0.23 → 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 +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/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
@@ -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,58 @@ 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
+ const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
262
+ onRequestId: (nextRequestId) => {
263
+ requestId = nextRequestId;
264
+ },
265
+ }).then((result) => {
266
+ if (!isCallToolResult(result)) {
267
+ throw new McpError(ERROR_INVALID_REQUEST, "Invalid tool result");
268
+ }
269
+ return result;
270
+ });
271
+ if (options.signal === undefined) {
272
+ return await requestPromise;
273
+ }
274
+ const signal = options.signal;
275
+ let abortListener;
276
+ const abortPromise = new Promise((_, reject) => {
277
+ const rejectWithAbortReason = () => {
278
+ if (requestId !== undefined) {
279
+ messageLayer.sendNotification("notifications/cancelled", { requestId });
280
+ }
281
+ reject(signal.reason);
282
+ };
283
+ abortListener = rejectWithAbortReason;
284
+ signal.addEventListener("abort", abortListener, { once: true });
285
+ if (signal.aborted) {
286
+ signal.removeEventListener("abort", abortListener);
287
+ rejectWithAbortReason();
288
+ }
289
+ });
290
+ try {
291
+ return (await Promise.race([requestPromise, abortPromise]));
292
+ }
293
+ finally {
294
+ if (abortListener !== undefined) {
295
+ signal.removeEventListener("abort", abortListener);
231
296
  }
232
- reject(signal.reason);
233
- };
234
- abortListener = rejectWithAbortReason;
235
- signal.addEventListener("abort", abortListener, { once: true });
236
- if (signal.aborted) {
237
- signal.removeEventListener("abort", abortListener);
238
- rejectWithAbortReason();
239
297
  }
240
- });
241
- try {
242
- return (await Promise.race([requestPromise, abortPromise]));
243
298
  }
244
299
  finally {
245
- if (abortListener !== undefined) {
246
- signal.removeEventListener("abort", abortListener);
300
+ if (options.progressToken !== undefined) {
301
+ const activeCount = this.activeProgressTokens.get(options.progressToken);
302
+ if (activeCount === 1) {
303
+ this.activeProgressTokens.delete(options.progressToken);
304
+ }
305
+ else if (activeCount !== undefined) {
306
+ this.activeProgressTokens.set(options.progressToken, activeCount - 1);
307
+ }
247
308
  }
248
309
  }
249
310
  }
@@ -280,6 +341,7 @@ export class McpClient {
280
341
  throw new Error("Server does not support resource subscriptions");
281
342
  }
282
343
  await messageLayer.sendRequest("resources/subscribe", { uri });
344
+ this.subscribedResourceUris.add(uri);
283
345
  }
284
346
  async unsubscribe(uri) {
285
347
  const messageLayer = this.getMessageLayerOrThrow();
@@ -288,6 +350,7 @@ export class McpClient {
288
350
  throw new Error("Server does not support resource subscriptions");
289
351
  }
290
352
  await messageLayer.sendRequest("resources/unsubscribe", { uri });
353
+ this.subscribedResourceUris.delete(uri);
291
354
  }
292
355
  async listPrompts(params = {}) {
293
356
  const messageLayer = this.getMessageLayerOrThrow();
@@ -332,6 +395,9 @@ export class McpClient {
332
395
  }
333
396
  async sendRootsChanged() {
334
397
  const messageLayer = this.getMessageLayerOrThrow();
398
+ if (this.currentClientCapabilities?.roots?.listChanged !== true) {
399
+ throw new Error("Client did not advertise roots list changes");
400
+ }
335
401
  messageLayer.sendNotification("notifications/roots/list_changed");
336
402
  }
337
403
  async ping() {
@@ -347,6 +413,12 @@ export class McpClient {
347
413
  this.transport?.dispose(closeError);
348
414
  this.messageLayer = null;
349
415
  this.transport = null;
416
+ this.currentServerCapabilities = null;
417
+ this.currentClientCapabilities = null;
418
+ this.currentServerInfo = null;
419
+ this.currentInstructions = undefined;
420
+ this.subscribedResourceUris.clear();
421
+ this.activeProgressTokens.clear();
350
422
  this.currentState = "closed";
351
423
  }
352
424
  }
@@ -1543,16 +1615,29 @@ export class HttpTransport {
1543
1615
  this.disposed = true;
1544
1616
  this.abortInFlightFetches();
1545
1617
  this.cancelOpenSseReaders();
1546
- this.terminateSession();
1547
1618
  if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
1548
1619
  this.writeStream.end();
1549
1620
  }
1550
1621
  if (!this.readStream.destroyed && !this.readStream.writableEnded) {
1551
1622
  this.readStream.end();
1552
1623
  }
1624
+ void this.closeWithSessionTermination(reason);
1625
+ }
1626
+ async closeWithSessionTermination(reason) {
1627
+ let closeReason = reason;
1628
+ if (this.sessionId !== undefined) {
1629
+ const sessionId = this.sessionId;
1630
+ this.sessionId = undefined;
1631
+ try {
1632
+ await this.sendSessionTerminationRequest(sessionId);
1633
+ }
1634
+ catch (error) {
1635
+ closeReason = error instanceof Error ? error : new Error(String(error));
1636
+ }
1637
+ }
1553
1638
  const resolveClosed = this.resolveClosed;
1554
1639
  this.resolveClosed = undefined;
1555
- resolveClosed?.({ reason });
1640
+ resolveClosed?.({ reason: closeReason });
1556
1641
  }
1557
1642
  abortInFlightFetches() {
1558
1643
  for (const abortController of this.inFlightFetchAbortControllers) {
@@ -1598,7 +1683,9 @@ export class HttpTransport {
1598
1683
  await this.throwForPostHttpError(response);
1599
1684
  this.captureSessionId(response);
1600
1685
  this.maybeOpenGetSseStream();
1601
- await this.forwardResponseMessages(response);
1686
+ void this.forwardResponseMessages(response).catch((error) => {
1687
+ this.dispose(error instanceof Error ? error : new Error(String(error)));
1688
+ });
1602
1689
  }
1603
1690
  }
1604
1691
  async createPostHeaders() {
@@ -1607,6 +1694,7 @@ export class HttpTransport {
1607
1694
  headers.set("Content-Type", "application/json");
1608
1695
  if (this.sessionId !== undefined) {
1609
1696
  headers.set("Mcp-Session-Id", this.sessionId);
1697
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1610
1698
  }
1611
1699
  return this.authorizeRequestHeaders(headers);
1612
1700
  }
@@ -1615,6 +1703,7 @@ export class HttpTransport {
1615
1703
  headers.set("Accept", "text/event-stream");
1616
1704
  if (this.sessionId !== undefined) {
1617
1705
  headers.set("Mcp-Session-Id", this.sessionId);
1706
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1618
1707
  }
1619
1708
  if (this.lastEventId !== undefined) {
1620
1709
  headers.set("Last-Event-ID", this.lastEventId);
@@ -1624,6 +1713,7 @@ export class HttpTransport {
1624
1713
  async createDeleteHeaders(sessionId) {
1625
1714
  const headers = new Headers(this.headers);
1626
1715
  headers.set("Mcp-Session-Id", sessionId);
1716
+ headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
1627
1717
  return this.authorizeRequestHeaders(headers);
1628
1718
  }
1629
1719
  async authorizeRequestHeaders(headers) {
@@ -1639,6 +1729,9 @@ export class HttpTransport {
1639
1729
  if (sessionId === null || sessionId.length === 0) {
1640
1730
  return;
1641
1731
  }
1732
+ if (this.sessionId !== undefined && this.sessionId !== sessionId) {
1733
+ throw new Error("HTTP transport response changed active session ID");
1734
+ }
1642
1735
  this.sessionId = sessionId;
1643
1736
  }
1644
1737
  maybeOpenGetSseStream() {
@@ -1653,22 +1746,20 @@ export class HttpTransport {
1653
1746
  this.dispose(error instanceof Error ? error : new Error(String(error)));
1654
1747
  });
1655
1748
  }
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
1749
  async sendSessionTerminationRequest(sessionId) {
1665
1750
  const response = await this.fetchImpl(this.url, {
1666
1751
  method: "DELETE",
1667
1752
  headers: await this.createDeleteHeaders(sessionId),
1668
1753
  });
1669
- if (response.status === 405) {
1754
+ if (response.status === 405 || response.ok) {
1670
1755
  return;
1671
1756
  }
1757
+ const responseBody = (await response.text()).trim();
1758
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
1759
+ const message = responseBody.length === 0
1760
+ ? `HTTP transport DELETE failed (${statusDescriptor})`
1761
+ : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
1762
+ throw new Error(message);
1672
1763
  }
1673
1764
  async consumeGetSseStream() {
1674
1765
  const response = await this.fetchWithOAuthRetry({
@@ -1678,6 +1769,18 @@ export class HttpTransport {
1678
1769
  if (response.status === 405) {
1679
1770
  throw new HttpTransportGetSseNotSupportedError();
1680
1771
  }
1772
+ if (response.status === 404) {
1773
+ this.sessionId = undefined;
1774
+ throw new Error("HTTP transport session expired (GET 404 response)");
1775
+ }
1776
+ if (!response.ok) {
1777
+ const responseBody = (await response.text()).trim();
1778
+ const statusDescriptor = `${response.status} ${response.statusText}`.trim();
1779
+ const message = responseBody.length === 0
1780
+ ? `HTTP transport GET failed (${statusDescriptor})`
1781
+ : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
1782
+ throw new Error(message);
1783
+ }
1681
1784
  const contentType = response.headers.get("Content-Type");
1682
1785
  if (contentType === null) {
1683
1786
  return;
@@ -1688,7 +1791,9 @@ export class HttpTransport {
1688
1791
  if (!this.disposed && this.sessionId !== undefined && this.lastEventId !== undefined) {
1689
1792
  this.maybeOpenGetSseStream();
1690
1793
  }
1794
+ return;
1691
1795
  }
1796
+ return;
1692
1797
  }
1693
1798
  async throwForPostHttpError(response) {
1694
1799
  if (response.status < 400) {
@@ -1744,7 +1849,9 @@ export class HttpTransport {
1744
1849
  }
1745
1850
  if (normalizedContentType.includes("application/json")) {
1746
1851
  await this.forwardJsonResponseMessage(response);
1852
+ return;
1747
1853
  }
1854
+ throw new Error("HTTP transport POST returned an unsupported response content type");
1748
1855
  }
1749
1856
  async forwardSseResponseMessages(response) {
1750
1857
  if (response.body === null) {
@@ -1866,8 +1973,12 @@ function normalizeLine(line) {
1866
1973
  }
1867
1974
  export async function* readLines(stream) {
1868
1975
  let buffer = "";
1976
+ const decoder = new TextDecoder();
1869
1977
  for await (const chunk of stream) {
1870
- buffer += chunkToString(chunk);
1978
+ buffer +=
1979
+ chunk instanceof Uint8Array
1980
+ ? decoder.decode(chunk, { stream: true })
1981
+ : decoder.decode() + String(chunk);
1871
1982
  while (true) {
1872
1983
  const newlineIndex = buffer.indexOf("\n");
1873
1984
  if (newlineIndex === -1) {
@@ -1878,6 +1989,7 @@ export async function* readLines(stream) {
1878
1989
  yield normalizeLine(line);
1879
1990
  }
1880
1991
  }
1992
+ buffer += decoder.decode();
1881
1993
  if (buffer.length > 0) {
1882
1994
  yield normalizeLine(buffer);
1883
1995
  }
@@ -2253,6 +2365,64 @@ export class JsonRpcMessageLayer {
2253
2365
  function isObjectRecord(value) {
2254
2366
  return typeof value === "object" && value !== null && !Array.isArray(value);
2255
2367
  }
2368
+ function isInitializeResult(value) {
2369
+ if (!isObjectRecord(value) || typeof value.protocolVersion !== "string") {
2370
+ return false;
2371
+ }
2372
+ if (!isServerCapabilities(value.capabilities)) {
2373
+ return false;
2374
+ }
2375
+ if (!isObjectRecord(value.serverInfo)
2376
+ || typeof value.serverInfo.name !== "string"
2377
+ || value.serverInfo.name.length === 0
2378
+ || typeof value.serverInfo.version !== "string"
2379
+ || value.serverInfo.version.length === 0) {
2380
+ return false;
2381
+ }
2382
+ return value.instructions === undefined || typeof value.instructions === "string";
2383
+ }
2384
+ function isServerCapabilities(value) {
2385
+ if (!isObjectRecord(value)) {
2386
+ return false;
2387
+ }
2388
+ for (const capability of ["prompts", "resources", "tools", "logging", "completions", "experimental"]) {
2389
+ if (value[capability] !== undefined && !isObjectRecord(value[capability])) {
2390
+ return false;
2391
+ }
2392
+ }
2393
+ return true;
2394
+ }
2395
+ function isCallToolResult(value) {
2396
+ if (!isObjectRecord(value) || !Array.isArray(value.content)) {
2397
+ return false;
2398
+ }
2399
+ if (value.isError !== undefined && typeof value.isError !== "boolean") {
2400
+ return false;
2401
+ }
2402
+ return value.content.every(isContentItem);
2403
+ }
2404
+ function isToolsListResult(value) {
2405
+ return isObjectRecord(value)
2406
+ && Array.isArray(value.tools)
2407
+ && (value.nextCursor === undefined || typeof value.nextCursor === "string");
2408
+ }
2409
+ function isContentItem(value) {
2410
+ if (!isObjectRecord(value)) {
2411
+ return false;
2412
+ }
2413
+ if (value.type === "text") {
2414
+ return typeof value.text === "string";
2415
+ }
2416
+ if (value.type === "image" || value.type === "audio") {
2417
+ return typeof value.data === "string" && typeof value.mimeType === "string";
2418
+ }
2419
+ if (value.type !== "resource" || !isObjectRecord(value.resource)) {
2420
+ return false;
2421
+ }
2422
+ return typeof value.resource.uri === "string"
2423
+ && (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
2424
+ && (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
2425
+ }
2256
2426
  function hasOwn(value, property) {
2257
2427
  return Object.prototype.hasOwnProperty.call(value, property);
2258
2428
  }
@@ -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", () => {