@pentoshi/clai 4.14.0 → 4.14.1

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 (269) hide show
  1. package/CONTRIBUTING.md +4 -1
  2. package/MCP.md +182 -0
  3. package/PROVIDERS.md +67 -0
  4. package/README.md +11 -2
  5. package/dist/agent/runner.js +1 -1
  6. package/dist/agent/runner.js.map +1 -1
  7. package/dist/agent/stream-recovery.js +5 -3
  8. package/dist/agent/stream-recovery.js.map +1 -1
  9. package/dist/agent/turn/mcp-agent-tools.d.ts +4 -2
  10. package/dist/agent/turn/mcp-agent-tools.js +15 -6
  11. package/dist/agent/turn/mcp-agent-tools.js.map +1 -1
  12. package/dist/agent/turn/tool-call-preparation.d.ts +2 -1
  13. package/dist/agent/turn/tool-call-preparation.js +20 -4
  14. package/dist/agent/turn/tool-call-preparation.js.map +1 -1
  15. package/dist/agent/turn/tool-execution/single-tool.js +1 -1
  16. package/dist/agent/turn/tool-execution/single-tool.js.map +1 -1
  17. package/dist/app/commands/catalog.js +1 -0
  18. package/dist/app/commands/catalog.js.map +1 -1
  19. package/dist/classic/app/action-handlers.js +2 -1
  20. package/dist/classic/app/action-handlers.js.map +1 -1
  21. package/dist/classic/app/app-wiring.js +2 -0
  22. package/dist/classic/app/app-wiring.js.map +1 -1
  23. package/dist/classic/chrome/composer-controller.d.ts +2 -1
  24. package/dist/classic/chrome/composer-controller.js +36 -19
  25. package/dist/classic/chrome/composer-controller.js.map +1 -1
  26. package/dist/classic/chrome/composer-frame.js +1 -1
  27. package/dist/classic/chrome/composer-frame.js.map +1 -1
  28. package/dist/classic/input/input-router.d.ts +2 -1
  29. package/dist/classic/input/input-router.js +9 -0
  30. package/dist/classic/input/input-router.js.map +1 -1
  31. package/dist/classic/input/key-event.d.ts +3 -0
  32. package/dist/classic/input/key-event.js.map +1 -1
  33. package/dist/classic/input/paste-burst.js +5 -0
  34. package/dist/classic/input/paste-burst.js.map +1 -1
  35. package/dist/classic/input/raw-decoder.d.ts +1 -0
  36. package/dist/classic/input/raw-decoder.js +15 -10
  37. package/dist/classic/input/raw-decoder.js.map +1 -1
  38. package/dist/classic/input/terminal-sequences.d.ts +0 -1
  39. package/dist/classic/input/terminal-sequences.js +0 -1
  40. package/dist/classic/input/terminal-sequences.js.map +1 -1
  41. package/dist/commands/providers.js +2 -0
  42. package/dist/commands/providers.js.map +1 -1
  43. package/dist/llm/adapters/mistral-messages.d.ts +1 -0
  44. package/dist/llm/adapters/mistral-messages.js +56 -0
  45. package/dist/llm/adapters/mistral-messages.js.map +1 -0
  46. package/dist/llm/capabilities.js +63 -1
  47. package/dist/llm/capabilities.js.map +1 -1
  48. package/dist/llm/capability/state.d.ts +1 -1
  49. package/dist/llm/capability/tool-dialect.js +8 -1
  50. package/dist/llm/capability/tool-dialect.js.map +1 -1
  51. package/dist/llm/capability/vision-patterns.js +6 -0
  52. package/dist/llm/capability/vision-patterns.js.map +1 -1
  53. package/dist/llm/capability/vision-registry.js +11 -1
  54. package/dist/llm/capability/vision-registry.js.map +1 -1
  55. package/dist/llm/catalog-context.d.ts +6 -0
  56. package/dist/llm/catalog-context.js +15 -0
  57. package/dist/llm/catalog-context.js.map +1 -0
  58. package/dist/llm/catalog-facts.d.ts +2 -0
  59. package/dist/llm/catalog-facts.js.map +1 -1
  60. package/dist/llm/cline-model-catalog.d.ts +4 -0
  61. package/dist/llm/cline-model-catalog.js +197 -0
  62. package/dist/llm/cline-model-catalog.js.map +1 -0
  63. package/dist/llm/cline.d.ts +1 -0
  64. package/dist/llm/cline.js +69 -17
  65. package/dist/llm/cline.js.map +1 -1
  66. package/dist/llm/free-model-limits.d.ts +1 -0
  67. package/dist/llm/free-model-limits.js +101 -0
  68. package/dist/llm/free-model-limits.js.map +1 -0
  69. package/dist/llm/free.js +136 -103
  70. package/dist/llm/free.js.map +1 -1
  71. package/dist/llm/mistral-models.d.ts +2 -0
  72. package/dist/llm/mistral-models.js +23 -0
  73. package/dist/llm/mistral-models.js.map +1 -0
  74. package/dist/llm/mistral.d.ts +2 -0
  75. package/dist/llm/mistral.js +129 -0
  76. package/dist/llm/mistral.js.map +1 -0
  77. package/dist/llm/profile/layer-merge.js +3 -3
  78. package/dist/llm/profile/layer-merge.js.map +1 -1
  79. package/dist/llm/provider-identity.js +5 -0
  80. package/dist/llm/provider-identity.js.map +1 -1
  81. package/dist/llm/provider-info-text.js +19 -0
  82. package/dist/llm/provider-info-text.js.map +1 -1
  83. package/dist/llm/provider-model-layers.js +45 -0
  84. package/dist/llm/provider-model-layers.js.map +1 -1
  85. package/dist/llm/provider-profile-layers.js +25 -0
  86. package/dist/llm/provider-profile-layers.js.map +1 -1
  87. package/dist/llm/provider-profiles.js +28 -4
  88. package/dist/llm/provider-profiles.js.map +1 -1
  89. package/dist/llm/quota-signals.d.ts +1 -0
  90. package/dist/llm/quota-signals.js +8 -0
  91. package/dist/llm/quota-signals.js.map +1 -1
  92. package/dist/llm/reasoning-capability.js +1 -0
  93. package/dist/llm/reasoning-capability.js.map +1 -1
  94. package/dist/llm/request-plan.js +15 -6
  95. package/dist/llm/request-plan.js.map +1 -1
  96. package/dist/llm/router.js +1 -1
  97. package/dist/llm/router.js.map +1 -1
  98. package/dist/llm/routing/attempt-complete.js +4 -3
  99. package/dist/llm/routing/attempt-complete.js.map +1 -1
  100. package/dist/llm/routing/attempt-request.d.ts +5 -1
  101. package/dist/llm/routing/attempt-request.js +22 -2
  102. package/dist/llm/routing/attempt-request.js.map +1 -1
  103. package/dist/llm/routing/attempt-stream.js +4 -3
  104. package/dist/llm/routing/attempt-stream.js.map +1 -1
  105. package/dist/llm/routing/failure-report.js +6 -2
  106. package/dist/llm/routing/failure-report.js.map +1 -1
  107. package/dist/llm/routing/provider-selection.js +3 -0
  108. package/dist/llm/routing/provider-selection.js.map +1 -1
  109. package/dist/llm/tool-protocol.d.ts +1 -1
  110. package/dist/llm/tool-protocol.js +4 -1
  111. package/dist/llm/tool-protocol.js.map +1 -1
  112. package/dist/llm/wire/chat-body.js +51 -28
  113. package/dist/llm/wire/chat-body.js.map +1 -1
  114. package/dist/llm/wire/effort-preflight.js +4 -1
  115. package/dist/llm/wire/effort-preflight.js.map +1 -1
  116. package/dist/llm/wire/mistral-content.d.ts +19 -0
  117. package/dist/llm/wire/mistral-content.js +106 -0
  118. package/dist/llm/wire/mistral-content.js.map +1 -0
  119. package/dist/llm/wire/mistral-model-catalog.d.ts +2 -0
  120. package/dist/llm/wire/mistral-model-catalog.js +73 -0
  121. package/dist/llm/wire/mistral-model-catalog.js.map +1 -0
  122. package/dist/llm/wire/openai-complete.js +10 -4
  123. package/dist/llm/wire/openai-complete.js.map +1 -1
  124. package/dist/llm/wire/openai-stream.js +38 -16
  125. package/dist/llm/wire/openai-stream.js.map +1 -1
  126. package/dist/mcp/auth/device.d.ts +7 -3
  127. package/dist/mcp/auth/device.js +55 -32
  128. package/dist/mcp/auth/device.js.map +1 -1
  129. package/dist/mcp/auth/http.d.ts +12 -0
  130. package/dist/mcp/auth/http.js +62 -0
  131. package/dist/mcp/auth/http.js.map +1 -0
  132. package/dist/mcp/auth/loopback.d.ts +9 -1
  133. package/dist/mcp/auth/loopback.js +80 -8
  134. package/dist/mcp/auth/loopback.js.map +1 -1
  135. package/dist/mcp/auth/metadata.d.ts +2 -3
  136. package/dist/mcp/auth/metadata.js +25 -25
  137. package/dist/mcp/auth/metadata.js.map +1 -1
  138. package/dist/mcp/auth/provider.d.ts +1 -0
  139. package/dist/mcp/auth/provider.js +215 -42
  140. package/dist/mcp/auth/provider.js.map +1 -1
  141. package/dist/mcp/auth/registration.d.ts +2 -3
  142. package/dist/mcp/auth/registration.js +10 -14
  143. package/dist/mcp/auth/registration.js.map +1 -1
  144. package/dist/mcp/auth/token-exchange.d.ts +5 -3
  145. package/dist/mcp/auth/token-exchange.js +15 -15
  146. package/dist/mcp/auth/token-exchange.js.map +1 -1
  147. package/dist/mcp/auth/token-store.d.ts +1 -1
  148. package/dist/mcp/auth/token-store.js +105 -54
  149. package/dist/mcp/auth/token-store.js.map +1 -1
  150. package/dist/mcp/auth/types.d.ts +11 -2
  151. package/dist/mcp/catalog.d.ts +11 -0
  152. package/dist/mcp/catalog.js +58 -0
  153. package/dist/mcp/catalog.js.map +1 -0
  154. package/dist/mcp/client.d.ts +10 -1
  155. package/dist/mcp/client.js +93 -29
  156. package/dist/mcp/client.js.map +1 -1
  157. package/dist/mcp/coerce.js +4 -4
  158. package/dist/mcp/coerce.js.map +1 -1
  159. package/dist/mcp/config-file.js +53 -10
  160. package/dist/mcp/config-file.js.map +1 -1
  161. package/dist/mcp/discovery.js +2 -0
  162. package/dist/mcp/discovery.js.map +1 -1
  163. package/dist/mcp/jsonrpc.js +28 -18
  164. package/dist/mcp/jsonrpc.js.map +1 -1
  165. package/dist/mcp/manager.d.ts +17 -0
  166. package/dist/mcp/manager.js +211 -29
  167. package/dist/mcp/manager.js.map +1 -1
  168. package/dist/mcp/results.js +25 -10
  169. package/dist/mcp/results.js.map +1 -1
  170. package/dist/mcp/runtime.d.ts +6 -6
  171. package/dist/mcp/runtime.js +168 -60
  172. package/dist/mcp/runtime.js.map +1 -1
  173. package/dist/mcp/schema-compact.js +3 -29
  174. package/dist/mcp/schema-compact.js.map +1 -1
  175. package/dist/mcp/storage-lock.d.ts +1 -0
  176. package/dist/mcp/storage-lock.js +80 -0
  177. package/dist/mcp/storage-lock.js.map +1 -0
  178. package/dist/mcp/transport-http.d.ts +13 -2
  179. package/dist/mcp/transport-http.js +245 -77
  180. package/dist/mcp/transport-http.js.map +1 -1
  181. package/dist/mcp/transport-stdio.d.ts +5 -2
  182. package/dist/mcp/transport-stdio.js +96 -52
  183. package/dist/mcp/transport-stdio.js.map +1 -1
  184. package/dist/mcp/transport.d.ts +9 -1
  185. package/dist/mcp/transport.js +38 -0
  186. package/dist/mcp/transport.js.map +1 -1
  187. package/dist/mcp/types.d.ts +8 -2
  188. package/dist/mcp/types.js +1 -5
  189. package/dist/mcp/types.js.map +1 -1
  190. package/dist/mcp/validation.js +81 -12
  191. package/dist/mcp/validation.js.map +1 -1
  192. package/dist/noninteractive/stream-renderer.d.ts +1 -0
  193. package/dist/noninteractive/stream-renderer.js +13 -0
  194. package/dist/noninteractive/stream-renderer.js.map +1 -1
  195. package/dist/store/config/endpoints.js +1 -0
  196. package/dist/store/config/endpoints.js.map +1 -1
  197. package/dist/store/config.js +2 -0
  198. package/dist/store/config.js.map +1 -1
  199. package/dist/store/rtk-usage.d.ts +1 -0
  200. package/dist/store/rtk-usage.js +8 -3
  201. package/dist/store/rtk-usage.js.map +1 -1
  202. package/dist/tools/definitions/aggregate.js +4 -1
  203. package/dist/tools/definitions/aggregate.js.map +1 -1
  204. package/dist/tools/rtk/binary.d.ts +2 -2
  205. package/dist/tools/rtk/binary.js +12 -4
  206. package/dist/tools/rtk/binary.js.map +1 -1
  207. package/dist/tools/rtk/rewrite.js +13 -3
  208. package/dist/tools/rtk/rewrite.js.map +1 -1
  209. package/dist/tui-v2/components/composer/composer-input-box.d.ts +6 -1
  210. package/dist/tui-v2/components/composer/composer-input-box.js +27 -18
  211. package/dist/tui-v2/components/composer/composer-input-box.js.map +1 -1
  212. package/dist/tui-v2/components/composer/paste-chip.d.ts +3 -2
  213. package/dist/tui-v2/components/composer/paste-chip.js +17 -48
  214. package/dist/tui-v2/components/composer/paste-chip.js.map +1 -1
  215. package/dist/tui-v2/composer/composer-cursor.d.ts +3 -0
  216. package/dist/tui-v2/composer/composer-cursor.js +21 -0
  217. package/dist/tui-v2/composer/composer-cursor.js.map +1 -0
  218. package/dist/tui-v2/composer/composer-editor.js +91 -100
  219. package/dist/tui-v2/composer/composer-editor.js.map +1 -1
  220. package/dist/tui-v2/composer/composer-highlight.d.ts +6 -1
  221. package/dist/tui-v2/composer/composer-highlight.js +9 -2
  222. package/dist/tui-v2/composer/composer-highlight.js.map +1 -1
  223. package/dist/tui-v2/composer/composer-wheel.js +2 -1
  224. package/dist/tui-v2/composer/composer-wheel.js.map +1 -1
  225. package/dist/tui-v2/composer/expand-paste.d.ts +4 -0
  226. package/dist/tui-v2/composer/expand-paste.js +34 -0
  227. package/dist/tui-v2/composer/expand-paste.js.map +1 -0
  228. package/dist/tui-v2/composer/paste-hit-test.d.ts +28 -0
  229. package/dist/tui-v2/composer/paste-hit-test.js +25 -0
  230. package/dist/tui-v2/composer/paste-hit-test.js.map +1 -0
  231. package/dist/tui-v2/composer/use-draft-actions.js +3 -1
  232. package/dist/tui-v2/composer/use-draft-actions.js.map +1 -1
  233. package/dist/tui-v2/input/paste-burst-guard.js +3 -0
  234. package/dist/tui-v2/input/paste-burst-guard.js.map +1 -1
  235. package/dist/types.d.ts +4 -3
  236. package/dist/types.js +1 -0
  237. package/dist/types.js.map +1 -1
  238. package/dist/ui/mentions.js +12 -1
  239. package/dist/ui/mentions.js.map +1 -1
  240. package/dist/ui-core/actions/action-id.d.ts +1 -1
  241. package/dist/ui-core/actions/action-id.js +1 -0
  242. package/dist/ui-core/actions/action-id.js.map +1 -1
  243. package/dist/ui-core/actions/format-shortcuts.js +3 -2
  244. package/dist/ui-core/actions/format-shortcuts.js.map +1 -1
  245. package/dist/ui-core/actions/keymap.js +2 -0
  246. package/dist/ui-core/actions/keymap.js.map +1 -1
  247. package/dist/ui-core/bootstrap/composition-root.js +29 -11
  248. package/dist/ui-core/bootstrap/composition-root.js.map +1 -1
  249. package/dist/ui-core/commands/mcp-commands.js +3 -3
  250. package/dist/ui-core/commands/mcp-commands.js.map +1 -1
  251. package/dist/ui-core/commands/pickers/search-reasoning.js +21 -6
  252. package/dist/ui-core/commands/pickers/search-reasoning.js.map +1 -1
  253. package/dist/ui-core/commands/rtk-commands.js +3 -3
  254. package/dist/ui-core/commands/rtk-commands.js.map +1 -1
  255. package/dist/ui-core/composer/completion.d.ts +1 -0
  256. package/dist/ui-core/composer/completion.js +31 -3
  257. package/dist/ui-core/composer/completion.js.map +1 -1
  258. package/dist/ui-core/composer/composer-height.d.ts +1 -1
  259. package/dist/ui-core/composer/composer-height.js +26 -3
  260. package/dist/ui-core/composer/composer-height.js.map +1 -1
  261. package/dist/ui-core/composer/paste-placeholder.d.ts +10 -0
  262. package/dist/ui-core/composer/paste-placeholder.js +60 -12
  263. package/dist/ui-core/composer/paste-placeholder.js.map +1 -1
  264. package/dist/ui-core/input/paste-burst.d.ts +2 -1
  265. package/dist/ui-core/input/paste-burst.js +20 -0
  266. package/dist/ui-core/input/paste-burst.js.map +1 -1
  267. package/dist/version.generated.d.ts +2 -2
  268. package/dist/version.generated.js +2 -2
  269. package/package.json +2 -1
@@ -1,5 +1,5 @@
1
- import { isJsonRpcResponse, parseMessage, SseDecoder, } from "./jsonrpc.js";
2
- import { McpTransportError, withTimeout, } from "./transport.js";
1
+ import { createNotification, isJsonRpcResponse, parseMessage, SseDecoder } from "./jsonrpc.js";
2
+ import { McpTransportError, awaitMcpOperation, dispatchServerMessage, withTimeout, } from "./transport.js";
3
3
  import { parseWwwAuthenticate } from "./auth/www-authenticate.js";
4
4
  import { MCP_PROTOCOL_VERSION, } from "./types.js";
5
5
  const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
@@ -36,14 +36,10 @@ async function rejectRedirect(response) {
36
36
  throw error;
37
37
  }
38
38
  function sseStreamError(server, status) {
39
- return new McpTransportError("network", status === 401
40
- ? unauthorizedHint(server)
41
- : `MCP SSE stream returned ${status}.`, status);
39
+ return new McpTransportError("network", status === 401 ? unauthorizedHint(server) : `MCP SSE stream returned ${status}.`, status);
42
40
  }
43
41
  function ssePostError(server, status) {
44
- return new McpTransportError("network", status === 401
45
- ? unauthorizedHint(server)
46
- : `MCP SSE POST returned ${status}.`, status);
42
+ return new McpTransportError("network", status === 401 ? unauthorizedHint(server) : `MCP SSE POST returned ${status}.`, status);
47
43
  }
48
44
  async function readBoundedText(response, maxBytes) {
49
45
  const body = response.body;
@@ -80,14 +76,13 @@ function baseHeaders(sessionId, protocolVersion, accept) {
80
76
  headers[PROTOCOL_HEADER] = protocolVersion;
81
77
  return headers;
82
78
  }
83
- async function collectSseResponse(response, targetId, maxBytes) {
79
+ async function collectSseResponse(response, targetId, maxBytes, onMessage, onEvent) {
84
80
  const body = response.body;
85
81
  if (!body) {
86
82
  throw new McpTransportError("protocol", "MCP SSE response had no body.");
87
83
  }
88
84
  const reader = body.getReader();
89
- const sse = new SseDecoder();
90
- let total = 0;
85
+ const sse = new SseDecoder(maxBytes);
91
86
  try {
92
87
  while (true) {
93
88
  const { done, value } = await reader.read();
@@ -95,22 +90,21 @@ async function collectSseResponse(response, targetId, maxBytes) {
95
90
  break;
96
91
  if (!value)
97
92
  continue;
98
- total += value.byteLength;
99
- if (total > maxBytes) {
100
- throw new McpTransportError("too-large", "MCP SSE stream exceeded the size limit.");
101
- }
102
93
  for (const event of sse.push(value)) {
94
+ onEvent(event.id);
103
95
  const message = parseMessage(event.data);
104
96
  if (message && isJsonRpcResponse(message) && String(message.id) === String(targetId)) {
105
97
  return message;
106
98
  }
99
+ if (message)
100
+ await onMessage(message);
107
101
  }
108
102
  }
109
103
  }
110
104
  finally {
111
105
  await reader.cancel().catch(() => undefined);
112
106
  }
113
- throw new McpTransportError("protocol", "MCP SSE stream closed before a matching response arrived.");
107
+ throw new McpTransportError("closed", "MCP SSE stream closed before a matching response arrived.");
114
108
  }
115
109
  function unauthorizedHint(server) {
116
110
  return (`MCP server ${server} returned 401 Unauthorized. ` +
@@ -124,6 +118,10 @@ export class StreamableHttpTransport {
124
118
  protocolVersion = MCP_PROTOCOL_VERSION;
125
119
  nextId = 1;
126
120
  closed = false;
121
+ handlers;
122
+ lifetime = new AbortController();
123
+ listening = false;
124
+ sentTokens = new WeakMap();
127
125
  constructor(config, options = {}) {
128
126
  this.config = config;
129
127
  this.options = options;
@@ -140,35 +138,49 @@ export class StreamableHttpTransport {
140
138
  setProtocolVersion(version) {
141
139
  this.protocolVersion = version;
142
140
  }
141
+ setHandlers(handlers) {
142
+ this.handlers = handlers;
143
+ }
143
144
  start() {
144
145
  return Promise.resolve();
145
146
  }
146
147
  async resolveHeaders(accept) {
147
148
  const base = baseHeaders(this.session, this.protocolVersion, accept);
148
- const authHeaders = this.options.authProvider
149
- ? await this.options.authProvider.headers()
150
- : {};
149
+ const authHeaders = this.options.authProvider ? await this.options.authProvider.headers() : {};
151
150
  return {
152
151
  ...base,
153
152
  ...authHeaders,
154
153
  ...this.config.headers,
155
154
  };
156
155
  }
157
- async sendOnce(accept, method, body, signal) {
158
- const headers = await this.resolveHeaders(accept);
159
- const init = { method, headers, redirect: "manual", signal };
156
+ async sendOnce(accept, method, body, signal, extraHeaders) {
157
+ const headers = await awaitMcpOperation(this.resolveHeaders(accept), signal);
158
+ const init = {
159
+ method,
160
+ headers: { ...headers, ...extraHeaders },
161
+ redirect: "manual",
162
+ signal,
163
+ };
160
164
  if (body !== undefined)
161
165
  init.body = body;
162
- return this.fetchImpl(this.config.url, init);
166
+ const response = await this.fetchImpl(this.config.url, init);
167
+ const authorization = new Headers(headers).get("authorization");
168
+ if (authorization)
169
+ this.sentTokens.set(response, authorization.replace(/^Bearer\s+/i, ""));
170
+ return response;
163
171
  }
164
- async retryOnUnauthorized(response, resend) {
172
+ async retryOnUnauthorized(response, resend, signal) {
165
173
  const provider = this.options.authProvider;
166
174
  if (!provider)
167
175
  return response;
168
176
  const challenge = parseWwwAuthenticate(response.headers.get("www-authenticate"));
169
177
  if (response.body)
170
178
  await response.body.cancel().catch(() => undefined);
171
- const refreshed = await provider.onUnauthorized(challenge);
179
+ const refreshed = await provider.onUnauthorized(challenge, {
180
+ interactive: false,
181
+ signal,
182
+ rejectedToken: this.sentTokens.get(response),
183
+ });
172
184
  if (!refreshed)
173
185
  return response;
174
186
  return resend();
@@ -187,38 +199,70 @@ export class StreamableHttpTransport {
187
199
  const id = this.nextId++;
188
200
  const framed = { ...message, id };
189
201
  const timeoutMs = options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
190
- const { signal, dispose } = withTimeout(options.signal, timeoutMs);
202
+ const parent = options.signal
203
+ ? AbortSignal.any([options.signal, this.lifetime.signal])
204
+ : this.lifetime.signal;
205
+ const { signal, dispose } = withTimeout(parent, timeoutMs);
191
206
  const accept = "application/json, text/event-stream";
192
207
  const body = JSON.stringify(framed);
193
208
  const resend = () => this.sendOnce(accept, "POST", body, signal);
194
209
  try {
195
210
  let response = await resend();
196
- this.captureSession(response);
197
- const redirect = redirectError(response);
198
- if (redirect)
199
- throw redirect;
200
- if (response.status === 401) {
201
- response = await this.retryOnUnauthorized(response, resend);
211
+ if (framed.method === "initialize" && response.ok)
202
212
  this.captureSession(response);
203
- const retryRedirect = redirectError(response);
204
- if (retryRedirect)
205
- throw retryRedirect;
213
+ await rejectRedirect(response);
214
+ if (response.status === 401) {
215
+ response = await this.retryOnUnauthorized(response, resend, signal);
216
+ if (framed.method === "initialize" && response.ok)
217
+ this.captureSession(response);
218
+ await rejectRedirect(response);
206
219
  }
207
220
  if (!response.ok)
208
221
  await this.failForStatus(response);
209
222
  const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
210
223
  if (contentType.includes("text/event-stream")) {
211
- return await collectSseResponse(response, framed.id, this.maxBytes);
224
+ let lastEventId;
225
+ for (let attempt = 0;; attempt++) {
226
+ try {
227
+ return await collectSseResponse(response, framed.id, this.maxBytes, (message) => dispatchServerMessage(message, this.handlers, (reply) => this.postResponse(reply, signal)), (eventId) => {
228
+ if (eventId !== undefined)
229
+ lastEventId = eventId;
230
+ });
231
+ }
232
+ catch (error) {
233
+ if (signal.aborted ||
234
+ !lastEventId ||
235
+ attempt >= 3 ||
236
+ (error instanceof McpTransportError &&
237
+ error.kind !== "closed" &&
238
+ error.kind !== "network"))
239
+ throw error;
240
+ response = await this.sendOnce("text/event-stream", "GET", undefined, signal, {
241
+ "last-event-id": lastEventId,
242
+ });
243
+ await rejectRedirect(response);
244
+ if (!response.ok)
245
+ await this.failForStatus(response);
246
+ }
247
+ }
212
248
  }
213
249
  const text = await readBoundedText(response, this.maxBytes);
214
250
  const parsed = parseMessage(text);
215
251
  if (!parsed || !isJsonRpcResponse(parsed)) {
216
252
  throw new McpTransportError("protocol", "MCP HTTP response was not a JSON-RPC response.");
217
253
  }
254
+ if (String(parsed.id) !== String(framed.id))
255
+ throw new McpTransportError("protocol", "MCP HTTP response id did not match the request.");
218
256
  return parsed;
219
257
  }
220
258
  catch (error) {
221
- throw mapFetchError(error);
259
+ if (signal.aborted && message.method !== "initialize" && !this.closed) {
260
+ void this.notify(createNotification("notifications/cancelled", {
261
+ requestId: id,
262
+ reason: "Client stopped waiting for this request.",
263
+ }), { timeoutMs: 1_000 }).catch(() => undefined);
264
+ }
265
+ throw mapFetchError(error, signal);
222
266
  }
223
267
  finally {
224
268
  dispose();
@@ -228,18 +272,24 @@ export class StreamableHttpTransport {
228
272
  if (this.closed)
229
273
  throw new McpTransportError("closed", "MCP HTTP transport closed.");
230
274
  const timeoutMs = options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
231
- const { signal, dispose } = withTimeout(options.signal, timeoutMs);
275
+ const parent = options.signal
276
+ ? AbortSignal.any([options.signal, this.lifetime.signal])
277
+ : this.lifetime.signal;
278
+ const { signal, dispose } = withTimeout(parent, timeoutMs);
232
279
  try {
233
- const response = await this.sendOnce("application/json, text/event-stream", "POST", JSON.stringify(message), signal);
234
- this.captureSession(response);
235
- const redirect = redirectError(response);
236
- if (redirect)
237
- throw redirect;
280
+ const resend = () => this.sendOnce("application/json, text/event-stream", "POST", JSON.stringify(message), signal);
281
+ let response = await resend();
282
+ await rejectRedirect(response);
283
+ if (response.status === 401)
284
+ response = await this.retryOnUnauthorized(response, resend, signal);
285
+ await rejectRedirect(response);
286
+ if (!response.ok)
287
+ await this.failForStatus(response);
238
288
  if (response.body)
239
289
  await response.body.cancel().catch(() => undefined);
240
290
  }
241
291
  catch (error) {
242
- throw mapFetchError(error);
292
+ throw mapFetchError(error, signal);
243
293
  }
244
294
  finally {
245
295
  dispose();
@@ -247,17 +297,74 @@ export class StreamableHttpTransport {
247
297
  }
248
298
  captureSession(response) {
249
299
  const id = response.headers.get(SESSION_HEADER);
250
- if (id && id.length > 0)
300
+ if (id && !/^[\x21-\x7e]+$/.test(id))
301
+ throw new McpTransportError("protocol", "MCP server returned an invalid session id.");
302
+ if (id)
251
303
  this.session = id;
252
304
  }
305
+ async postResponse(message, signal) {
306
+ const response = await this.sendOnce("application/json, text/event-stream", "POST", JSON.stringify(message), signal);
307
+ await rejectRedirect(response);
308
+ if (!response.ok)
309
+ await this.failForStatus(response);
310
+ await response.body?.cancel().catch(() => undefined);
311
+ }
312
+ listen() {
313
+ if (this.listening || this.closed)
314
+ return;
315
+ this.listening = true;
316
+ void this.pumpNotifications().finally(() => {
317
+ this.listening = false;
318
+ });
319
+ }
320
+ async pumpNotifications() {
321
+ let lastEventId;
322
+ for (let attempt = 0; !this.closed && attempt < 4; attempt++) {
323
+ try {
324
+ const response = await this.sendOnce("text/event-stream", "GET", undefined, this.lifetime.signal, lastEventId ? { "last-event-id": lastEventId } : undefined);
325
+ await rejectRedirect(response);
326
+ if (!response.ok || !response.body) {
327
+ await response.body?.cancel().catch(() => undefined);
328
+ return;
329
+ }
330
+ const decoder = new SseDecoder(this.maxBytes);
331
+ const reader = response.body.getReader();
332
+ try {
333
+ for (;;) {
334
+ const { value, done } = await reader.read();
335
+ if (done)
336
+ break;
337
+ if (!value)
338
+ continue;
339
+ for (const event of decoder.push(value)) {
340
+ if (event.id !== undefined)
341
+ lastEventId = event.id;
342
+ const message = parseMessage(event.data);
343
+ if (message)
344
+ await dispatchServerMessage(message, this.handlers, (reply) => this.postResponse(reply, this.lifetime.signal));
345
+ }
346
+ }
347
+ }
348
+ finally {
349
+ await reader.cancel().catch(() => undefined);
350
+ }
351
+ if (!lastEventId)
352
+ return;
353
+ }
354
+ catch {
355
+ return;
356
+ }
357
+ }
358
+ }
253
359
  async close() {
254
360
  if (this.closed)
255
361
  return;
256
362
  this.closed = true;
363
+ this.lifetime.abort(new McpTransportError("closed", "MCP HTTP transport closed."));
257
364
  if (!this.session)
258
365
  return;
259
366
  try {
260
- const { signal, dispose } = withTimeout(undefined, DEFAULT_REQUEST_TIMEOUT_MS);
367
+ const { signal, dispose } = withTimeout(undefined, 2_000);
261
368
  try {
262
369
  const response = await this.sendOnce("application/json", "DELETE", undefined, signal);
263
370
  if (response.body)
@@ -284,6 +391,8 @@ export class LegacySseTransport {
284
391
  pending = new Map();
285
392
  readyPromise;
286
393
  pumpError;
394
+ handlers;
395
+ sentTokens = new WeakMap();
287
396
  constructor(config, options = {}) {
288
397
  this.config = config;
289
398
  this.options = options;
@@ -300,23 +409,46 @@ export class LegacySseTransport {
300
409
  setProtocolVersion(version) {
301
410
  this.protocolVersion = version;
302
411
  }
303
- start() {
412
+ setHandlers(handlers) {
413
+ this.handlers = handlers;
414
+ }
415
+ start(options = {}) {
416
+ if (this.closed)
417
+ return Promise.reject(new McpTransportError("closed", "MCP SSE transport closed."));
304
418
  if (this.readyPromise)
305
419
  return this.readyPromise;
306
420
  this.readyPromise = new Promise((resolve, reject) => {
421
+ const { signal, dispose } = withTimeout(options.signal, options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
307
422
  let settled = false;
308
423
  const onReady = () => {
309
424
  if (settled)
310
425
  return;
311
426
  settled = true;
427
+ dispose();
428
+ signal.removeEventListener("abort", onAbort);
312
429
  resolve();
313
430
  };
314
431
  const onFail = (error) => {
315
432
  if (settled)
316
433
  return;
317
434
  settled = true;
435
+ dispose();
436
+ signal.removeEventListener("abort", onAbort);
318
437
  reject(error);
319
438
  };
439
+ const onAbort = () => {
440
+ const error = signal.reason instanceof McpTransportError
441
+ ? signal.reason
442
+ : new McpTransportError("cancelled", "MCP SSE connection cancelled.");
443
+ this.pumpError = error;
444
+ this.streamController.abort(error);
445
+ onFail(error);
446
+ };
447
+ if (signal.aborted) {
448
+ onAbort();
449
+ return;
450
+ }
451
+ signal.addEventListener("abort", onAbort, { once: true });
320
452
  this.pump(onReady, onFail).catch((error) => onFail(mapFetchError(error)));
321
453
  });
322
454
  return this.readyPromise;
@@ -325,19 +457,21 @@ export class LegacySseTransport {
325
457
  const base = { accept };
326
458
  if (this.protocolVersion)
327
459
  base[PROTOCOL_HEADER] = this.protocolVersion;
328
- const authHeaders = this.options.authProvider
329
- ? await this.options.authProvider.headers()
330
- : {};
460
+ const authHeaders = this.options.authProvider ? await this.options.authProvider.headers() : {};
331
461
  return { ...base, ...authHeaders, ...this.config.headers };
332
462
  }
333
463
  async openStream() {
334
- const headers = await this.streamHeaders("text/event-stream");
335
- return this.fetchImpl(this.config.url, {
464
+ const headers = await awaitMcpOperation(this.streamHeaders("text/event-stream"), this.streamController.signal);
465
+ const response = await this.fetchImpl(this.config.url, {
336
466
  method: "GET",
337
467
  headers,
338
468
  redirect: "manual",
339
469
  signal: this.streamController.signal,
340
470
  });
471
+ const authorization = new Headers(headers).get("authorization");
472
+ if (authorization)
473
+ this.sentTokens.set(response, authorization.replace(/^Bearer\s+/i, ""));
474
+ return response;
341
475
  }
342
476
  async openAuthorizedStream() {
343
477
  let response = await this.openStream();
@@ -348,7 +482,11 @@ export class LegacySseTransport {
348
482
  const challenge = parseWwwAuthenticate(response.headers.get("www-authenticate"));
349
483
  if (response.body)
350
484
  await response.body.cancel().catch(() => undefined);
351
- const refreshed = await provider.onUnauthorized(challenge);
485
+ const refreshed = await provider.onUnauthorized(challenge, {
486
+ interactive: false,
487
+ signal: this.streamController.signal,
488
+ rejectedToken: this.sentTokens.get(response),
489
+ });
352
490
  if (!refreshed)
353
491
  return response;
354
492
  response = await this.openStream();
@@ -369,8 +507,7 @@ export class LegacySseTransport {
369
507
  return;
370
508
  }
371
509
  const reader = response.body.getReader();
372
- const sse = new SseDecoder();
373
- let total = 0;
510
+ const sse = new SseDecoder(this.maxBytes);
374
511
  try {
375
512
  while (true) {
376
513
  const { done, value } = await reader.read();
@@ -378,15 +515,14 @@ export class LegacySseTransport {
378
515
  break;
379
516
  if (!value)
380
517
  continue;
381
- total += value.byteLength;
382
- if (total > this.maxBytes) {
383
- throw new McpTransportError("too-large", "MCP SSE stream exceeded the size limit.");
384
- }
385
518
  for (const event of sse.push(value)) {
386
519
  this.handleEvent(event.event, event.data, onReady);
387
520
  }
388
521
  }
389
- this.failAll(new McpTransportError("closed", "MCP SSE stream closed."));
522
+ const closed = new McpTransportError("closed", "MCP SSE stream closed.");
523
+ this.pumpError = closed;
524
+ onFail(closed);
525
+ this.failAll(closed);
390
526
  }
391
527
  catch (error) {
392
528
  const mapped = mapFetchError(error);
@@ -401,17 +537,29 @@ export class LegacySseTransport {
401
537
  handleEvent(event, data, onReady) {
402
538
  if (event === "endpoint") {
403
539
  try {
404
- this.endpointUrl = new URL(data, this.config.url).toString();
540
+ const endpoint = new URL(data, this.config.url);
541
+ if (endpoint.origin !== new URL(this.config.url).origin ||
542
+ endpoint.username ||
543
+ endpoint.password) {
544
+ throw new McpTransportError("protocol", "MCP SSE announced an endpoint on another origin; credentials cannot be forwarded there.");
545
+ }
546
+ this.endpointUrl = endpoint.toString();
405
547
  onReady();
406
548
  }
407
- catch {
408
- void 0;
549
+ catch (error) {
550
+ throw error instanceof McpTransportError
551
+ ? error
552
+ : new McpTransportError("protocol", "MCP SSE announced an invalid endpoint.");
409
553
  }
410
554
  return;
411
555
  }
412
556
  const message = parseMessage(data);
413
- if (!message || !isJsonRpcResponse(message))
557
+ if (!message)
414
558
  return;
559
+ if (!isJsonRpcResponse(message)) {
560
+ void dispatchServerMessage(message, this.handlers, (reply) => this.postMessage(JSON.stringify(reply), this.streamController.signal)).catch(() => undefined);
561
+ return;
562
+ }
415
563
  const numericId = typeof message.id === "number" ? message.id : Number(message.id);
416
564
  if (!Number.isFinite(numericId))
417
565
  return;
@@ -433,29 +581,35 @@ export class LegacySseTransport {
433
581
  const base = { "content-type": "application/json" };
434
582
  if (this.protocolVersion)
435
583
  base[PROTOCOL_HEADER] = this.protocolVersion;
436
- const authHeaders = this.options.authProvider
437
- ? await this.options.authProvider.headers()
438
- : {};
584
+ const authHeaders = this.options.authProvider ? await this.options.authProvider.headers() : {};
439
585
  return { ...base, ...authHeaders, ...this.config.headers };
440
586
  }
441
587
  async sendPost(endpoint, body, signal) {
442
- const headers = await this.postHeaders();
443
- return this.fetchImpl(endpoint, {
588
+ const headers = await awaitMcpOperation(this.postHeaders(), signal);
589
+ const response = await this.fetchImpl(endpoint, {
444
590
  method: "POST",
445
591
  headers,
446
592
  body,
447
593
  signal,
448
594
  redirect: "manual",
449
595
  });
596
+ const authorization = new Headers(headers).get("authorization");
597
+ if (authorization)
598
+ this.sentTokens.set(response, authorization.replace(/^Bearer\s+/i, ""));
599
+ return response;
450
600
  }
451
- async retryPostUnauthorized(response, resend) {
601
+ async retryPostUnauthorized(response, resend, signal) {
452
602
  const provider = this.options.authProvider;
453
603
  if (response.status !== 401 || !provider)
454
604
  return response;
455
605
  const challenge = parseWwwAuthenticate(response.headers.get("www-authenticate"));
456
606
  if (response.body)
457
607
  await response.body.cancel().catch(() => undefined);
458
- const refreshed = await provider.onUnauthorized(challenge);
608
+ const refreshed = await provider.onUnauthorized(challenge, {
609
+ interactive: false,
610
+ signal,
611
+ rejectedToken: this.sentTokens.get(response),
612
+ });
459
613
  if (!refreshed)
460
614
  return response;
461
615
  const retried = await resend();
@@ -470,7 +624,7 @@ export class LegacySseTransport {
470
624
  const resend = () => this.sendPost(endpoint, body, signal);
471
625
  let response = await resend();
472
626
  await rejectRedirect(response);
473
- response = await this.retryPostUnauthorized(response, resend);
627
+ response = await this.retryPostUnauthorized(response, resend, signal);
474
628
  if (response.body)
475
629
  await response.body.cancel().catch(() => undefined);
476
630
  if (!response.ok)
@@ -479,19 +633,28 @@ export class LegacySseTransport {
479
633
  async request(message, options = {}) {
480
634
  if (this.closed)
481
635
  throw new McpTransportError("closed", "MCP SSE transport closed.");
482
- await this.start();
636
+ await this.start(options);
483
637
  if (this.pumpError)
484
638
  throw this.pumpError;
485
639
  const id = this.nextId++;
486
640
  const framed = { ...message, id };
487
641
  const timeoutMs = options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
488
- const { signal, dispose } = withTimeout(options.signal, timeoutMs);
642
+ const parent = options.signal
643
+ ? AbortSignal.any([options.signal, this.streamController.signal])
644
+ : this.streamController.signal;
645
+ const { signal, dispose } = withTimeout(parent, timeoutMs);
489
646
  return await new Promise((resolve, reject) => {
490
647
  const onAbort = () => {
491
648
  const entry = this.pending.get(id);
492
649
  if (entry) {
493
650
  this.pending.delete(id);
494
651
  entry.dispose();
652
+ if (message.method !== "initialize") {
653
+ void this.notify(createNotification("notifications/cancelled", {
654
+ requestId: id,
655
+ reason: "Client stopped waiting for this request.",
656
+ }), { timeoutMs: 1_000 }).catch(() => undefined);
657
+ }
495
658
  }
496
659
  const reason = signal.reason;
497
660
  reject(reason instanceof McpTransportError
@@ -515,16 +678,21 @@ export class LegacySseTransport {
515
678
  this.pending.delete(id);
516
679
  entry.dispose();
517
680
  }
518
- reject(mapFetchError(error));
681
+ reject(mapFetchError(error, signal));
519
682
  });
520
683
  });
521
684
  }
522
685
  async notify(message, options = {}) {
523
686
  if (this.closed)
524
687
  throw new McpTransportError("closed", "MCP SSE transport closed.");
525
- await this.start();
688
+ await this.start(options);
689
+ if (this.pumpError)
690
+ throw this.pumpError;
526
691
  const timeoutMs = options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
527
- const { signal, dispose } = withTimeout(options.signal, timeoutMs);
692
+ const parent = options.signal
693
+ ? AbortSignal.any([options.signal, this.streamController.signal])
694
+ : this.streamController.signal;
695
+ const { signal, dispose } = withTimeout(parent, timeoutMs);
528
696
  try {
529
697
  await this.postMessage(JSON.stringify(message), signal);
530
698
  }