@strands-agents/sdk 1.0.0 → 1.2.0

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 (284) hide show
  1. package/README.md +6 -0
  2. package/dist/src/__fixtures__/agent-helpers.d.ts +16 -1
  3. package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
  4. package/dist/src/__fixtures__/agent-helpers.js +42 -0
  5. package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
  6. package/dist/src/__fixtures__/tool-helpers.d.ts +2 -1
  7. package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
  8. package/dist/src/__fixtures__/tool-helpers.js +20 -3
  9. package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
  10. package/dist/src/__tests__/interrupt.test.d.ts +2 -0
  11. package/dist/src/__tests__/interrupt.test.d.ts.map +1 -0
  12. package/dist/src/__tests__/interrupt.test.js +264 -0
  13. package/dist/src/__tests__/interrupt.test.js.map +1 -0
  14. package/dist/src/__tests__/mcp.test.js +447 -7
  15. package/dist/src/__tests__/mcp.test.js.map +1 -1
  16. package/dist/src/agent/__tests__/agent.hook.test.js +551 -1
  17. package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
  18. package/dist/src/agent/__tests__/agent.interrupt.test.d.ts +2 -0
  19. package/dist/src/agent/__tests__/agent.interrupt.test.d.ts.map +1 -0
  20. package/dist/src/agent/__tests__/agent.interrupt.test.js +779 -0
  21. package/dist/src/agent/__tests__/agent.interrupt.test.js.map +1 -0
  22. package/dist/src/agent/__tests__/agent.model-retry.test.d.ts +2 -0
  23. package/dist/src/agent/__tests__/agent.model-retry.test.d.ts.map +1 -0
  24. package/dist/src/agent/__tests__/agent.model-retry.test.js +161 -0
  25. package/dist/src/agent/__tests__/agent.model-retry.test.js.map +1 -0
  26. package/dist/src/agent/__tests__/agent.test.js +174 -0
  27. package/dist/src/agent/__tests__/agent.test.js.map +1 -1
  28. package/dist/src/agent/__tests__/snapshot.test.js +148 -4
  29. package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
  30. package/dist/src/agent/agent-as-tool.d.ts.map +1 -1
  31. package/dist/src/agent/agent-as-tool.js +2 -3
  32. package/dist/src/agent/agent-as-tool.js.map +1 -1
  33. package/dist/src/agent/agent.d.ts +94 -4
  34. package/dist/src/agent/agent.d.ts.map +1 -1
  35. package/dist/src/agent/agent.js +625 -223
  36. package/dist/src/agent/agent.js.map +1 -1
  37. package/dist/src/agent/snapshot.d.ts +11 -19
  38. package/dist/src/agent/snapshot.d.ts.map +1 -1
  39. package/dist/src/agent/snapshot.js +23 -19
  40. package/dist/src/agent/snapshot.js.map +1 -1
  41. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +230 -9
  42. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -1
  43. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +19 -6
  44. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +1 -1
  45. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +422 -41
  46. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
  47. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +75 -1
  48. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
  49. package/dist/src/conversation-manager/conversation-manager.d.ts +67 -22
  50. package/dist/src/conversation-manager/conversation-manager.d.ts.map +1 -1
  51. package/dist/src/conversation-manager/conversation-manager.js +65 -13
  52. package/dist/src/conversation-manager/conversation-manager.js.map +1 -1
  53. package/dist/src/conversation-manager/index.d.ts +1 -1
  54. package/dist/src/conversation-manager/index.d.ts.map +1 -1
  55. package/dist/src/conversation-manager/index.js +1 -1
  56. package/dist/src/conversation-manager/index.js.map +1 -1
  57. package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts +43 -10
  58. package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
  59. package/dist/src/conversation-manager/sliding-window-conversation-manager.js +202 -45
  60. package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
  61. package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts +23 -1
  62. package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts.map +1 -1
  63. package/dist/src/conversation-manager/summarizing-conversation-manager.js +39 -17
  64. package/dist/src/conversation-manager/summarizing-conversation-manager.js.map +1 -1
  65. package/dist/src/hooks/__tests__/events.test.js +99 -12
  66. package/dist/src/hooks/__tests__/events.test.js.map +1 -1
  67. package/dist/src/hooks/__tests__/registry.test.js +166 -2
  68. package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
  69. package/dist/src/hooks/events.d.ts +125 -32
  70. package/dist/src/hooks/events.d.ts.map +1 -1
  71. package/dist/src/hooks/events.js +111 -8
  72. package/dist/src/hooks/events.js.map +1 -1
  73. package/dist/src/hooks/index.d.ts +4 -3
  74. package/dist/src/hooks/index.d.ts.map +1 -1
  75. package/dist/src/hooks/index.js +2 -1
  76. package/dist/src/hooks/index.js.map +1 -1
  77. package/dist/src/hooks/registry.d.ts +12 -12
  78. package/dist/src/hooks/registry.d.ts.map +1 -1
  79. package/dist/src/hooks/registry.js +55 -15
  80. package/dist/src/hooks/registry.js.map +1 -1
  81. package/dist/src/hooks/types.d.ts +23 -0
  82. package/dist/src/hooks/types.d.ts.map +1 -1
  83. package/dist/src/hooks/types.js +17 -1
  84. package/dist/src/hooks/types.js.map +1 -1
  85. package/dist/src/index.d.ts +12 -6
  86. package/dist/src/index.d.ts.map +1 -1
  87. package/dist/src/index.js +7 -2
  88. package/dist/src/index.js.map +1 -1
  89. package/dist/src/interrupt.d.ts +247 -0
  90. package/dist/src/interrupt.d.ts.map +1 -0
  91. package/dist/src/interrupt.js +316 -0
  92. package/dist/src/interrupt.js.map +1 -0
  93. package/dist/src/mcp.d.ts +61 -4
  94. package/dist/src/mcp.d.ts.map +1 -1
  95. package/dist/src/mcp.js +161 -25
  96. package/dist/src/mcp.js.map +1 -1
  97. package/dist/src/models/__tests__/anthropic.test.js +78 -8
  98. package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
  99. package/dist/src/models/__tests__/bedrock.test.js +156 -18
  100. package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
  101. package/dist/src/models/__tests__/defaults.test.d.ts +2 -0
  102. package/dist/src/models/__tests__/defaults.test.d.ts.map +1 -0
  103. package/dist/src/models/__tests__/defaults.test.js +36 -0
  104. package/dist/src/models/__tests__/defaults.test.js.map +1 -0
  105. package/dist/src/models/__tests__/google.test.js +72 -6
  106. package/dist/src/models/__tests__/google.test.js.map +1 -1
  107. package/dist/src/models/anthropic.d.ts +10 -0
  108. package/dist/src/models/anthropic.d.ts.map +1 -1
  109. package/dist/src/models/anthropic.js +14 -4
  110. package/dist/src/models/anthropic.js.map +1 -1
  111. package/dist/src/models/bedrock.d.ts +17 -3
  112. package/dist/src/models/bedrock.d.ts.map +1 -1
  113. package/dist/src/models/bedrock.js +80 -13
  114. package/dist/src/models/bedrock.js.map +1 -1
  115. package/dist/src/models/defaults.d.ts +10 -0
  116. package/dist/src/models/defaults.d.ts.map +1 -1
  117. package/dist/src/models/defaults.js +129 -0
  118. package/dist/src/models/defaults.js.map +1 -1
  119. package/dist/src/models/google/model.d.ts.map +1 -1
  120. package/dist/src/models/google/model.js +4 -2
  121. package/dist/src/models/google/model.js.map +1 -1
  122. package/dist/src/models/google/types.d.ts +10 -0
  123. package/dist/src/models/google/types.d.ts.map +1 -1
  124. package/dist/src/models/model.d.ts +15 -0
  125. package/dist/src/models/model.d.ts.map +1 -1
  126. package/dist/src/models/model.js +18 -0
  127. package/dist/src/models/model.js.map +1 -1
  128. package/dist/src/models/openai/__tests__/chat.test.js +55 -2
  129. package/dist/src/models/openai/__tests__/chat.test.js.map +1 -1
  130. package/dist/src/models/openai/__tests__/responses.test.js +19 -0
  131. package/dist/src/models/openai/__tests__/responses.test.js.map +1 -1
  132. package/dist/src/models/openai/errors.d.ts.map +1 -1
  133. package/dist/src/models/openai/errors.js +7 -4
  134. package/dist/src/models/openai/errors.js.map +1 -1
  135. package/dist/src/models/openai/model.d.ts.map +1 -1
  136. package/dist/src/models/openai/model.js +2 -2
  137. package/dist/src/models/openai/model.js.map +1 -1
  138. package/dist/src/multiagent/__tests__/graph.test.js +69 -0
  139. package/dist/src/multiagent/__tests__/graph.test.js.map +1 -1
  140. package/dist/src/multiagent/__tests__/graph.tracer.test.js +14 -0
  141. package/dist/src/multiagent/__tests__/graph.tracer.test.js.map +1 -1
  142. package/dist/src/multiagent/__tests__/interrupts.test.d.ts +2 -0
  143. package/dist/src/multiagent/__tests__/interrupts.test.d.ts.map +1 -0
  144. package/dist/src/multiagent/__tests__/interrupts.test.js +390 -0
  145. package/dist/src/multiagent/__tests__/interrupts.test.js.map +1 -0
  146. package/dist/src/multiagent/__tests__/nodes.test.js +13 -0
  147. package/dist/src/multiagent/__tests__/nodes.test.js.map +1 -1
  148. package/dist/src/multiagent/__tests__/state.test.js +139 -1
  149. package/dist/src/multiagent/__tests__/state.test.js.map +1 -1
  150. package/dist/src/multiagent/__tests__/swarm.test.js +77 -0
  151. package/dist/src/multiagent/__tests__/swarm.test.js.map +1 -1
  152. package/dist/src/multiagent/events.d.ts +15 -1
  153. package/dist/src/multiagent/events.d.ts.map +1 -1
  154. package/dist/src/multiagent/events.js +18 -0
  155. package/dist/src/multiagent/events.js.map +1 -1
  156. package/dist/src/multiagent/graph.d.ts +59 -3
  157. package/dist/src/multiagent/graph.d.ts.map +1 -1
  158. package/dist/src/multiagent/graph.js +201 -34
  159. package/dist/src/multiagent/graph.js.map +1 -1
  160. package/dist/src/multiagent/multiagent.d.ts +77 -3
  161. package/dist/src/multiagent/multiagent.d.ts.map +1 -1
  162. package/dist/src/multiagent/multiagent.js +115 -1
  163. package/dist/src/multiagent/multiagent.js.map +1 -1
  164. package/dist/src/multiagent/nodes.d.ts +18 -0
  165. package/dist/src/multiagent/nodes.d.ts.map +1 -1
  166. package/dist/src/multiagent/nodes.js +69 -22
  167. package/dist/src/multiagent/nodes.js.map +1 -1
  168. package/dist/src/multiagent/state.d.ts +39 -3
  169. package/dist/src/multiagent/state.d.ts.map +1 -1
  170. package/dist/src/multiagent/state.js +80 -1
  171. package/dist/src/multiagent/state.js.map +1 -1
  172. package/dist/src/multiagent/swarm.d.ts +30 -1
  173. package/dist/src/multiagent/swarm.d.ts.map +1 -1
  174. package/dist/src/multiagent/swarm.js +166 -33
  175. package/dist/src/multiagent/swarm.js.map +1 -1
  176. package/dist/src/registry/__tests__/tool-registry.test.js +37 -0
  177. package/dist/src/registry/__tests__/tool-registry.test.js.map +1 -1
  178. package/dist/src/registry/tool-registry.d.ts +13 -7
  179. package/dist/src/registry/tool-registry.d.ts.map +1 -1
  180. package/dist/src/registry/tool-registry.js +35 -10
  181. package/dist/src/registry/tool-registry.js.map +1 -1
  182. package/dist/src/retry/__tests__/backoff-strategy.test.d.ts +2 -0
  183. package/dist/src/retry/__tests__/backoff-strategy.test.d.ts.map +1 -0
  184. package/dist/src/retry/__tests__/backoff-strategy.test.js +116 -0
  185. package/dist/src/retry/__tests__/backoff-strategy.test.js.map +1 -0
  186. package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts +2 -0
  187. package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts.map +1 -0
  188. package/dist/src/retry/__tests__/default-model-retry-strategy.test.js +225 -0
  189. package/dist/src/retry/__tests__/default-model-retry-strategy.test.js.map +1 -0
  190. package/dist/src/retry/backoff-strategy.d.ts +108 -0
  191. package/dist/src/retry/backoff-strategy.d.ts.map +1 -0
  192. package/dist/src/retry/backoff-strategy.js +86 -0
  193. package/dist/src/retry/backoff-strategy.js.map +1 -0
  194. package/dist/src/retry/default-model-retry-strategy.d.ts +76 -0
  195. package/dist/src/retry/default-model-retry-strategy.d.ts.map +1 -0
  196. package/dist/src/retry/default-model-retry-strategy.js +104 -0
  197. package/dist/src/retry/default-model-retry-strategy.js.map +1 -0
  198. package/dist/src/retry/index.d.ts +8 -0
  199. package/dist/src/retry/index.d.ts.map +1 -0
  200. package/dist/src/retry/index.js +7 -0
  201. package/dist/src/retry/index.js.map +1 -0
  202. package/dist/src/retry/model-retry-strategy.d.ts +80 -0
  203. package/dist/src/retry/model-retry-strategy.d.ts.map +1 -0
  204. package/dist/src/retry/model-retry-strategy.js +85 -0
  205. package/dist/src/retry/model-retry-strategy.js.map +1 -0
  206. package/dist/src/retry/retry-strategy.d.ts +34 -0
  207. package/dist/src/retry/retry-strategy.d.ts.map +1 -0
  208. package/dist/src/retry/retry-strategy.js +25 -0
  209. package/dist/src/retry/retry-strategy.js.map +1 -0
  210. package/dist/src/session/__tests__/session-manager.test.js +84 -3
  211. package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
  212. package/dist/src/session/session-manager.d.ts +11 -2
  213. package/dist/src/session/session-manager.d.ts.map +1 -1
  214. package/dist/src/session/session-manager.js +17 -6
  215. package/dist/src/session/session-manager.js.map +1 -1
  216. package/dist/src/telemetry/__tests__/meter.test.js +5 -27
  217. package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
  218. package/dist/src/telemetry/meter.d.ts +12 -4
  219. package/dist/src/telemetry/meter.d.ts.map +1 -1
  220. package/dist/src/telemetry/meter.js +13 -8
  221. package/dist/src/telemetry/meter.js.map +1 -1
  222. package/dist/src/tools/__tests__/tool.test.js +24 -1
  223. package/dist/src/tools/__tests__/tool.test.js.map +1 -1
  224. package/dist/src/tools/function-tool.d.ts.map +1 -1
  225. package/dist/src/tools/function-tool.js +6 -1
  226. package/dist/src/tools/function-tool.js.map +1 -1
  227. package/dist/src/tools/mcp-tool.d.ts.map +1 -1
  228. package/dist/src/tools/mcp-tool.js +3 -2
  229. package/dist/src/tools/mcp-tool.js.map +1 -1
  230. package/dist/src/tools/tool.d.ts +10 -1
  231. package/dist/src/tools/tool.d.ts.map +1 -1
  232. package/dist/src/tools/tool.js +12 -0
  233. package/dist/src/tools/tool.js.map +1 -1
  234. package/dist/src/tsconfig.tsbuildinfo +1 -1
  235. package/dist/src/types/__tests__/agent.test.js +97 -0
  236. package/dist/src/types/__tests__/agent.test.js.map +1 -1
  237. package/dist/src/types/agent.d.ts +48 -8
  238. package/dist/src/types/agent.d.ts.map +1 -1
  239. package/dist/src/types/agent.js +28 -3
  240. package/dist/src/types/agent.js.map +1 -1
  241. package/dist/src/types/interrupt.d.ts +103 -0
  242. package/dist/src/types/interrupt.d.ts.map +1 -0
  243. package/dist/src/types/interrupt.js +63 -0
  244. package/dist/src/types/interrupt.js.map +1 -0
  245. package/dist/src/types/messages.d.ts +2 -1
  246. package/dist/src/types/messages.d.ts.map +1 -1
  247. package/dist/src/types/messages.js.map +1 -1
  248. package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts +2 -0
  249. package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts.map +1 -0
  250. package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js +292 -0
  251. package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js.map +1 -0
  252. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts +2 -0
  253. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts.map +1 -0
  254. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js +148 -0
  255. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js.map +1 -0
  256. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts +2 -0
  257. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts.map +1 -0
  258. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js +78 -0
  259. package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js.map +1 -0
  260. package/dist/src/vended-plugins/context-offloader/index.d.ts +23 -0
  261. package/dist/src/vended-plugins/context-offloader/index.d.ts.map +1 -0
  262. package/dist/src/vended-plugins/context-offloader/index.js +21 -0
  263. package/dist/src/vended-plugins/context-offloader/index.js.map +1 -0
  264. package/dist/src/vended-plugins/context-offloader/plugin.d.ts +48 -0
  265. package/dist/src/vended-plugins/context-offloader/plugin.d.ts.map +1 -0
  266. package/dist/src/vended-plugins/context-offloader/plugin.js +244 -0
  267. package/dist/src/vended-plugins/context-offloader/plugin.js.map +1 -0
  268. package/dist/src/vended-plugins/context-offloader/storage.d.ts +114 -0
  269. package/dist/src/vended-plugins/context-offloader/storage.d.ts.map +1 -0
  270. package/dist/src/vended-plugins/context-offloader/storage.js +204 -0
  271. package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -0
  272. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +12 -0
  273. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +1 -1
  274. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +3 -0
  275. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
  276. package/dist/src/vended-tools/bash/bash.d.ts.map +1 -1
  277. package/dist/src/vended-tools/bash/bash.js +0 -3
  278. package/dist/src/vended-tools/bash/bash.js.map +1 -1
  279. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +3 -0
  280. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -1
  281. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +3 -0
  282. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
  283. package/dist/src/vended-tools/notebook/notebook.d.ts +1 -1
  284. package/package.json +9 -5
@@ -1,11 +1,14 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
3
  import { McpError, ErrorCode, ElicitRequestSchema, UrlElicitationRequiredError, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
+ import { ClientCredentialsProvider } from '@modelcontextprotocol/sdk/client/auth-extensions.js';
4
6
  import { McpClient } from '../mcp.js';
5
7
  import { McpTool } from '../tools/mcp-tool.js';
6
8
  import { JsonBlock } from '../types/messages.js';
7
9
  import { ImageBlock } from '../types/media.js';
8
10
  import { context, propagation, trace, TraceFlags } from '@opentelemetry/api';
11
+ import { logger } from '../logging/index.js';
9
12
  /**
10
13
  * Helper to create a mock async generator that yields a result message.
11
14
  * This simulates the behavior of callToolStream returning a stream that ends with a result.
@@ -15,6 +18,16 @@ function createMockCallToolStream(result) {
15
18
  yield { type: 'result', result };
16
19
  };
17
20
  }
21
+ vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => ({
22
+ StreamableHTTPClientTransport: vi.fn(function () {
23
+ return { start: vi.fn(), send: vi.fn(), close: vi.fn() };
24
+ }),
25
+ }));
26
+ vi.mock('@modelcontextprotocol/sdk/client/auth-extensions.js', () => ({
27
+ ClientCredentialsProvider: vi.fn(function () {
28
+ return { redirectUrl: undefined, clientMetadata: { client_id: 'test' } };
29
+ }),
30
+ }));
18
31
  vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
19
32
  Client: vi.fn(function () {
20
33
  return {
@@ -23,6 +36,10 @@ vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
23
36
  listTools: vi.fn(),
24
37
  callTool: vi.fn(),
25
38
  setRequestHandler: vi.fn(),
39
+ setNotificationHandler: vi.fn(),
40
+ getServerCapabilities: vi.fn(),
41
+ getServerVersion: vi.fn(),
42
+ getInstructions: vi.fn(),
26
43
  experimental: {
27
44
  tasks: {
28
45
  callToolStream: vi.fn(),
@@ -110,7 +127,11 @@ describe('MCP Integration', () => {
110
127
  sdkClientMock = vi.mocked(Client).mock.results[0].value;
111
128
  });
112
129
  it('initializes SDK client with correct configuration', () => {
113
- expect(Client).toHaveBeenCalledWith({ name: 'TestApp', version: '0.0.1' }, undefined);
130
+ expect(Client).toHaveBeenCalledWith({ name: 'TestApp', version: '0.0.1' }, expect.objectContaining({
131
+ listChanged: expect.objectContaining({
132
+ tools: expect.objectContaining({ autoRefresh: false, debounceMs: 300 }),
133
+ }),
134
+ }));
114
135
  });
115
136
  it('injects trace context into tool arguments when active span exists', async () => {
116
137
  mockActiveSpan();
@@ -198,17 +219,72 @@ describe('MCP Integration', () => {
198
219
  expect(tools[0]).toBeInstanceOf(McpTool);
199
220
  expect(tools[0].name).toBe('weather');
200
221
  });
222
+ it('paginates through all pages of tools', async () => {
223
+ sdkClientMock.listTools
224
+ .mockResolvedValueOnce({
225
+ tools: [{ name: 'tool_a', description: 'A', inputSchema: {} }],
226
+ nextCursor: 'page2',
227
+ })
228
+ .mockResolvedValueOnce({
229
+ tools: [{ name: 'tool_b', description: 'B', inputSchema: {} }],
230
+ nextCursor: 'page3',
231
+ })
232
+ .mockResolvedValueOnce({
233
+ tools: [{ name: 'tool_c', description: 'C', inputSchema: {} }],
234
+ });
235
+ const tools = await client.listTools();
236
+ expect(tools).toHaveLength(3);
237
+ expect(tools.map((t) => t.name)).toEqual(['tool_a', 'tool_b', 'tool_c']);
238
+ expect(sdkClientMock.listTools).toHaveBeenCalledTimes(3);
239
+ expect(sdkClientMock.listTools).toHaveBeenNthCalledWith(1, undefined);
240
+ expect(sdkClientMock.listTools).toHaveBeenNthCalledWith(2, { cursor: 'page2' });
241
+ expect(sdkClientMock.listTools).toHaveBeenNthCalledWith(3, { cursor: 'page3' });
242
+ });
243
+ it('generates description fallback when description is missing', async () => {
244
+ sdkClientMock.listTools.mockResolvedValue({
245
+ tools: [{ name: 'my_tool', inputSchema: {} }],
246
+ });
247
+ const tools = await client.listTools();
248
+ expect(tools[0].description).toBe('Tool which performs my_tool');
249
+ });
250
+ it('generates description fallback when description is empty string', async () => {
251
+ sdkClientMock.listTools.mockResolvedValue({
252
+ tools: [{ name: 'my_tool', description: '', inputSchema: {} }],
253
+ });
254
+ const tools = await client.listTools();
255
+ expect(tools[0].description).toBe('Tool which performs my_tool');
256
+ });
201
257
  it('uses callTool when tasksConfig is undefined (default)', async () => {
202
258
  const tool = new McpTool({ name: 'calc', description: '', inputSchema: {}, client });
203
259
  sdkClientMock.callTool.mockResolvedValue({ content: [] });
204
260
  await client.callTool(tool, { op: 'add' });
205
261
  expect(sdkClientMock.connect).toHaveBeenCalled();
206
- expect(sdkClientMock.callTool).toHaveBeenCalledWith({
207
- name: 'calc',
208
- arguments: { op: 'add' },
209
- });
262
+ expect(sdkClientMock.callTool).toHaveBeenCalledWith({ name: 'calc', arguments: { op: 'add' } }, undefined, undefined);
210
263
  expect(sdkClientMock.experimental.tasks.callToolStream).not.toHaveBeenCalled();
211
264
  });
265
+ it('forwards abort signal to SDK callTool', async () => {
266
+ const tool = new McpTool({ name: 'calc', description: '', inputSchema: {}, client });
267
+ sdkClientMock.callTool.mockResolvedValue({ content: [] });
268
+ const controller = new AbortController();
269
+ await client.callTool(tool, { op: 'add' }, { signal: controller.signal });
270
+ expect(sdkClientMock.callTool).toHaveBeenCalledWith({ name: 'calc', arguments: { op: 'add' } }, undefined, {
271
+ signal: controller.signal,
272
+ });
273
+ });
274
+ it('forwards abort signal to callToolStream when tasksConfig is provided', async () => {
275
+ const resultsLengthBefore = vi.mocked(Client).mock.results.length;
276
+ const taskClient = new McpClient({
277
+ applicationName: 'TestApp',
278
+ transport: mockTransport,
279
+ tasksConfig: {},
280
+ });
281
+ const taskSdkClientMock = vi.mocked(Client).mock.results[resultsLengthBefore].value;
282
+ const tool = new McpTool({ name: 'calc', description: '', inputSchema: {}, client: taskClient });
283
+ taskSdkClientMock.experimental.tasks.callToolStream.mockReturnValue(createMockCallToolStream({ content: [] })());
284
+ const controller = new AbortController();
285
+ await taskClient.callTool(tool, { op: 'add' }, { signal: controller.signal });
286
+ expect(taskSdkClientMock.experimental.tasks.callToolStream).toHaveBeenCalledWith({ name: 'calc', arguments: { op: 'add' } }, undefined, { timeout: 60000, maxTotalTimeout: 300000, resetTimeoutOnProgress: true, signal: controller.signal });
287
+ });
212
288
  it('uses callToolStream when tasksConfig is provided (empty object)', async () => {
213
289
  const resultsLengthBefore = vi.mocked(Client).mock.results.length;
214
290
  const taskClient = new McpClient({
@@ -246,6 +322,11 @@ describe('MCP Integration', () => {
246
322
  expect(sdkClientMock.close).toHaveBeenCalled();
247
323
  expect(mockTransport.close).toHaveBeenCalled();
248
324
  });
325
+ it('supports Symbol.asyncDispose for await using pattern', async () => {
326
+ await client[Symbol.asyncDispose]();
327
+ expect(sdkClientMock.close).toHaveBeenCalled();
328
+ expect(mockTransport.close).toHaveBeenCalled();
329
+ });
249
330
  it('registers elicitation handler before connecting when callback is provided', async () => {
250
331
  const resultsLengthBefore = vi.mocked(Client).mock.results.length;
251
332
  const callback = vi.fn();
@@ -273,7 +354,7 @@ describe('MCP Integration', () => {
273
354
  elicitationCallback: callback,
274
355
  });
275
356
  const lastCall = vi.mocked(Client).mock.calls.at(-1);
276
- expect(lastCall[1]).toEqual({ capabilities: { elicitation: { form: {}, url: {} } } });
357
+ expect(lastCall[1]).toEqual(expect.objectContaining({ capabilities: { elicitation: { form: {}, url: {} } } }));
277
358
  });
278
359
  it('elicitation handler returns accepted result with content', async () => {
279
360
  const callbackResult = { action: 'accept', content: { username: 'alice' } };
@@ -329,6 +410,98 @@ describe('MCP Integration', () => {
329
410
  await expect(handler(request, extra)).rejects.toThrow('User cancelled');
330
411
  });
331
412
  });
413
+ describe('tools list changed', () => {
414
+ let client;
415
+ let sdkClientMock;
416
+ beforeEach(() => {
417
+ client = new McpClient({ applicationName: 'TestApp', transport: mockTransport });
418
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
419
+ sdkClientMock.connect.mockResolvedValue(undefined);
420
+ });
421
+ function triggerToolsChanged() {
422
+ const ctorCall = vi.mocked(Client).mock.calls.at(-1);
423
+ ctorCall[1].listChanged.tools.onChanged(null, null);
424
+ }
425
+ it('calls onToolsChanged with old names and new tools when list changes', async () => {
426
+ sdkClientMock.listTools.mockResolvedValue({
427
+ tools: [{ name: 'tool_a', description: 'A', inputSchema: {} }],
428
+ });
429
+ await client.listTools();
430
+ const onToolsChanged = vi.fn();
431
+ client.onToolsChanged = onToolsChanged;
432
+ sdkClientMock.listTools.mockResolvedValue({
433
+ tools: [
434
+ { name: 'tool_a', description: 'A', inputSchema: {} },
435
+ { name: 'tool_b', description: 'B', inputSchema: {} },
436
+ ],
437
+ });
438
+ triggerToolsChanged();
439
+ await vi.waitFor(() => expect(onToolsChanged).toHaveBeenCalled());
440
+ expect(onToolsChanged).toHaveBeenCalledWith(['tool_a'], expect.any(Array));
441
+ const newTools = onToolsChanged.mock.calls[0][1];
442
+ expect(newTools.map((t) => t.name)).toEqual(['tool_a', 'tool_b']);
443
+ });
444
+ it('updates registered tool names after each listTools call', async () => {
445
+ sdkClientMock.listTools.mockResolvedValue({
446
+ tools: [
447
+ { name: 'x', description: 'X', inputSchema: {} },
448
+ { name: 'y', description: 'Y', inputSchema: {} },
449
+ ],
450
+ });
451
+ await client.listTools();
452
+ const onToolsChanged = vi.fn();
453
+ client.onToolsChanged = onToolsChanged;
454
+ sdkClientMock.listTools.mockResolvedValue({
455
+ tools: [{ name: 'z', description: 'Z', inputSchema: {} }],
456
+ });
457
+ triggerToolsChanged();
458
+ await vi.waitFor(() => expect(onToolsChanged).toHaveBeenCalled());
459
+ expect(onToolsChanged).toHaveBeenCalledWith(['x', 'y'], expect.any(Array));
460
+ const newTools = onToolsChanged.mock.calls[0][1];
461
+ expect(newTools.map((t) => t.name)).toEqual(['z']);
462
+ });
463
+ it('does not throw when onToolsChanged is not set', async () => {
464
+ sdkClientMock.listTools.mockResolvedValue({
465
+ tools: [{ name: 'tool_a', description: 'A', inputSchema: {} }],
466
+ });
467
+ await client.listTools();
468
+ sdkClientMock.listTools.mockResolvedValue({
469
+ tools: [{ name: 'tool_b', description: 'B', inputSchema: {} }],
470
+ });
471
+ triggerToolsChanged();
472
+ await new Promise((r) => setTimeout(r, 0));
473
+ });
474
+ it('logs warning and preserves registry when listTools fails during refresh', async () => {
475
+ sdkClientMock.listTools.mockResolvedValue({
476
+ tools: [{ name: 'tool_a', description: 'A', inputSchema: {} }],
477
+ });
478
+ await client.listTools();
479
+ const onToolsChanged = vi.fn();
480
+ client.onToolsChanged = onToolsChanged;
481
+ sdkClientMock.listTools.mockRejectedValue(new Error('server disconnected'));
482
+ const warnSpy = vi.spyOn(logger, 'warn');
483
+ triggerToolsChanged();
484
+ await vi.waitFor(() => expect(warnSpy).toHaveBeenCalled());
485
+ expect(onToolsChanged).not.toHaveBeenCalled();
486
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('failed to refresh tools'));
487
+ });
488
+ it('coalesces notifications received during an in-flight refresh into one extra refresh', async () => {
489
+ sdkClientMock.listTools.mockResolvedValue({
490
+ tools: [{ name: 'tool_a', description: 'A', inputSchema: {} }],
491
+ });
492
+ await client.listTools();
493
+ const onToolsChanged = vi.fn();
494
+ client.onToolsChanged = onToolsChanged;
495
+ let resolveListTools;
496
+ sdkClientMock.listTools.mockReturnValue(new Promise((r) => (resolveListTools = r)));
497
+ triggerToolsChanged();
498
+ triggerToolsChanged();
499
+ triggerToolsChanged();
500
+ resolveListTools({ tools: [{ name: 'tool_b', description: 'B', inputSchema: {} }] });
501
+ await vi.waitFor(() => expect(onToolsChanged).toHaveBeenCalledTimes(2));
502
+ expect(sdkClientMock.listTools).toHaveBeenCalledTimes(3);
503
+ });
504
+ });
332
505
  describe('McpTool', () => {
333
506
  const mockClientWrapper = { callTool: vi.fn() };
334
507
  const tool = new McpTool({
@@ -339,9 +512,21 @@ describe('MCP Integration', () => {
339
512
  });
340
513
  const toolContext = {
341
514
  toolUse: { toolUseId: 'id-123', name: 'weather', input: { city: 'NYC' } },
342
- agent: {},
515
+ agent: { cancelSignal: new AbortController().signal },
343
516
  invocationState: {},
517
+ interrupt: () => {
518
+ throw new Error('interrupt not available in mock context');
519
+ },
344
520
  };
521
+ it('forwards agent cancelSignal to callTool', async () => {
522
+ vi.mocked(mockClientWrapper.callTool).mockResolvedValue({
523
+ content: [{ type: 'text', text: 'ok' }],
524
+ });
525
+ await runTool(tool.stream(toolContext));
526
+ expect(mockClientWrapper.callTool).toHaveBeenCalledWith(tool, { city: 'NYC' }, {
527
+ signal: toolContext.agent.cancelSignal,
528
+ });
529
+ });
345
530
  it('returns text results on success', async () => {
346
531
  vi.mocked(mockClientWrapper.callTool).mockResolvedValue({
347
532
  content: [{ type: 'text', text: 'Sunny' }],
@@ -560,4 +745,259 @@ describe('MCP Integration', () => {
560
745
  });
561
746
  });
562
747
  });
748
+ describe('server metadata getters', () => {
749
+ let client;
750
+ let sdkClientMock;
751
+ beforeEach(() => {
752
+ vi.clearAllMocks();
753
+ client = new McpClient({ applicationName: 'TestApp', transport: mockTransport });
754
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
755
+ });
756
+ afterEach(() => {
757
+ vi.restoreAllMocks();
758
+ });
759
+ it('returns undefined for all getters before connect', () => {
760
+ sdkClientMock.getServerCapabilities.mockReturnValue(undefined);
761
+ sdkClientMock.getServerVersion.mockReturnValue(undefined);
762
+ sdkClientMock.getInstructions.mockReturnValue(undefined);
763
+ expect(client.serverCapabilities).toBeUndefined();
764
+ expect(client.serverVersion).toBeUndefined();
765
+ expect(client.serverInstructions).toBeUndefined();
766
+ });
767
+ it('returns serverCapabilities after connect', async () => {
768
+ const caps = { tools: {} };
769
+ sdkClientMock.getServerCapabilities.mockReturnValue(caps);
770
+ await client.connect();
771
+ expect(client.serverCapabilities).toBe(caps);
772
+ });
773
+ it('returns serverVersion after connect', async () => {
774
+ const version = { name: 'my-server', version: '1.2.3' };
775
+ sdkClientMock.getServerVersion.mockReturnValue(version);
776
+ await client.connect();
777
+ expect(client.serverVersion).toBe(version);
778
+ });
779
+ it('returns serverInstructions after connect', async () => {
780
+ sdkClientMock.getInstructions.mockReturnValue('Use this server for X.');
781
+ await client.connect();
782
+ expect(client.serverInstructions).toBe('Use this server for X.');
783
+ });
784
+ it('connectionState is disconnected before connect', () => {
785
+ expect(client.connectionState).toBe('disconnected');
786
+ });
787
+ it('connectionState is connected after successful connect', async () => {
788
+ await client.connect();
789
+ expect(client.connectionState).toBe('connected');
790
+ });
791
+ });
792
+ describe('failOpen', () => {
793
+ let sdkClientMock;
794
+ beforeEach(() => {
795
+ vi.clearAllMocks();
796
+ });
797
+ afterEach(() => {
798
+ vi.restoreAllMocks();
799
+ });
800
+ it('throws on connection failure by default', async () => {
801
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport });
802
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
803
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
804
+ await expect(client.connect()).rejects.toThrow('connection refused');
805
+ });
806
+ it('swallows connection failure when failOpen is true', async () => {
807
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
808
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
809
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
810
+ await expect(client.connect()).resolves.toBeUndefined();
811
+ });
812
+ it('logs a warning when failOpen swallows a connection failure', async () => {
813
+ const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => { });
814
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
815
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
816
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
817
+ await client.connect();
818
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('MCP server failed to connect'));
819
+ });
820
+ it('listTools returns empty array when failOpen and connection failed', async () => {
821
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
822
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
823
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
824
+ const tools = await client.listTools();
825
+ expect(tools).toEqual([]);
826
+ });
827
+ it('callTool throws when failOpen and connection failed', async () => {
828
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
829
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
830
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
831
+ const tool = new McpTool({ name: 'my_tool', description: '', inputSchema: {}, client });
832
+ await expect(client.callTool(tool, {})).rejects.toThrow('MCP server failed to connect. Call connect(true) to retry.');
833
+ });
834
+ it('does not retry connection on subsequent calls after failOpen failure', async () => {
835
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
836
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
837
+ sdkClientMock.connect.mockRejectedValue(new Error('connection refused'));
838
+ await client.listTools();
839
+ await client.listTools();
840
+ expect(sdkClientMock.connect).toHaveBeenCalledTimes(1);
841
+ });
842
+ it('recovers after explicit connect(true) when server comes back', async () => {
843
+ const client = new McpClient({ applicationName: 'TestApp', transport: mockTransport, failOpen: true });
844
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
845
+ sdkClientMock.connect.mockRejectedValueOnce(new Error('connection refused'));
846
+ sdkClientMock.listTools.mockResolvedValue({ tools: [] });
847
+ const firstTools = await client.listTools();
848
+ expect(firstTools).toEqual([]);
849
+ expect(client.connectionState).toBe('failed');
850
+ await client.connect(true);
851
+ const secondTools = await client.listTools();
852
+ expect(secondTools).toEqual([]);
853
+ expect(client.connectionState).toBe('connected');
854
+ expect(sdkClientMock.connect).toHaveBeenCalledTimes(2);
855
+ });
856
+ });
857
+ describe('log routing', () => {
858
+ let notificationHandler;
859
+ let sdkClientMock;
860
+ beforeEach(() => {
861
+ vi.clearAllMocks();
862
+ new McpClient({ applicationName: 'TestApp', transport: mockTransport });
863
+ sdkClientMock = vi.mocked(Client).mock.results.at(-1).value;
864
+ // Handler is registered in the constructor — read it from the first setNotificationHandler call
865
+ notificationHandler = sdkClientMock.setNotificationHandler.mock.calls[0][1];
866
+ });
867
+ afterEach(() => {
868
+ vi.restoreAllMocks();
869
+ });
870
+ it('routes debug level to logger.debug', () => {
871
+ const spy = vi.spyOn(logger, 'debug').mockImplementation(() => { });
872
+ notificationHandler({ params: { level: 'debug', data: 'hello' } });
873
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
874
+ });
875
+ it('routes info level to logger.info', () => {
876
+ const spy = vi.spyOn(logger, 'info').mockImplementation(() => { });
877
+ notificationHandler({ params: { level: 'info', data: 'hello' } });
878
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
879
+ });
880
+ it('routes notice level to logger.info', () => {
881
+ const spy = vi.spyOn(logger, 'info').mockImplementation(() => { });
882
+ notificationHandler({ params: { level: 'notice', data: 'hello' } });
883
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
884
+ });
885
+ it('routes warning level to logger.warn', () => {
886
+ const spy = vi.spyOn(logger, 'warn').mockImplementation(() => { });
887
+ notificationHandler({ params: { level: 'warning', data: 'hello' } });
888
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
889
+ });
890
+ it('routes error level to logger.error', () => {
891
+ const spy = vi.spyOn(logger, 'error').mockImplementation(() => { });
892
+ notificationHandler({ params: { level: 'error', data: 'hello' } });
893
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
894
+ });
895
+ it('routes critical level to logger.error', () => {
896
+ const spy = vi.spyOn(logger, 'error').mockImplementation(() => { });
897
+ notificationHandler({ params: { level: 'critical', data: 'hello' } });
898
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
899
+ });
900
+ it('routes alert level to logger.error', () => {
901
+ const spy = vi.spyOn(logger, 'error').mockImplementation(() => { });
902
+ notificationHandler({ params: { level: 'alert', data: 'hello' } });
903
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
904
+ });
905
+ it('routes emergency level to logger.error', () => {
906
+ const spy = vi.spyOn(logger, 'error').mockImplementation(() => { });
907
+ notificationHandler({ params: { level: 'emergency', data: 'hello' } });
908
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('hello'));
909
+ });
910
+ it('includes logger name and data in the message', () => {
911
+ const spy = vi.spyOn(logger, 'info').mockImplementation(() => { });
912
+ notificationHandler({ params: { level: 'info', logger: 'my-server', data: { key: 'val' } } });
913
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('my-server'));
914
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('key'));
915
+ });
916
+ it('calls custom logHandler when provided', () => {
917
+ const customHandler = vi.fn();
918
+ new McpClient({ applicationName: 'TestApp', transport: mockTransport, logHandler: customHandler });
919
+ const customSdkMock = vi.mocked(Client).mock.results.at(-1).value;
920
+ const capturedHandler = customSdkMock.setNotificationHandler.mock.calls[0][1];
921
+ const params = { level: 'info', data: 'test' };
922
+ capturedHandler({ params });
923
+ expect(customHandler).toHaveBeenCalledWith(params);
924
+ });
925
+ });
926
+ describe('McpClient transport resolution', () => {
927
+ beforeEach(() => {
928
+ vi.clearAllMocks();
929
+ });
930
+ it('constructs StreamableHTTPClientTransport when url is provided', () => {
931
+ new McpClient({ url: 'https://mcp.example.com' });
932
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL('https://mcp.example.com'), undefined);
933
+ });
934
+ it('constructs ClientCredentialsProvider when auth is provided', () => {
935
+ new McpClient({ url: 'https://mcp.example.com', auth: { clientId: 'id', clientSecret: 'secret' } });
936
+ expect(ClientCredentialsProvider).toHaveBeenCalledWith({ clientId: 'id', clientSecret: 'secret' });
937
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL('https://mcp.example.com'), {
938
+ authProvider: expect.anything(),
939
+ });
940
+ });
941
+ it('passes scopes as space-separated string', () => {
942
+ new McpClient({
943
+ url: 'https://mcp.example.com',
944
+ auth: { clientId: 'id', clientSecret: 'secret', scopes: ['read', 'write'] },
945
+ });
946
+ expect(ClientCredentialsProvider).toHaveBeenCalledWith({
947
+ clientId: 'id',
948
+ clientSecret: 'secret',
949
+ scope: 'read write',
950
+ });
951
+ });
952
+ it('passes custom authProvider to transport', () => {
953
+ const customProvider = { redirectUrl: undefined, clientMetadata: {} };
954
+ new McpClient({ url: 'https://mcp.example.com', authProvider: customProvider });
955
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL('https://mcp.example.com'), {
956
+ authProvider: customProvider,
957
+ });
958
+ });
959
+ it('throws when both transport and url are provided', () => {
960
+ expect(() => new McpClient({ transport: mockTransport, url: 'https://mcp.example.com' })).toThrow('provide either "transport" or "url", not both');
961
+ });
962
+ it('throws when neither transport nor url is provided', () => {
963
+ expect(() => new McpClient({})).toThrow('either "transport" or "url" must be provided');
964
+ });
965
+ it('throws when auth is provided with transport', () => {
966
+ expect(() => new McpClient({ transport: mockTransport, auth: { clientId: 'x', clientSecret: 'y' } })).toThrow('"auth", "authProvider", and "headers" require "url"');
967
+ });
968
+ it('throws when both auth and authProvider are provided', () => {
969
+ const customProvider = {};
970
+ expect(() => new McpClient({
971
+ url: 'https://mcp.example.com',
972
+ auth: { clientId: 'x', clientSecret: 'y' },
973
+ authProvider: customProvider,
974
+ })).toThrow('provide either "auth" or "authProvider", not both');
975
+ });
976
+ it('accepts URL instance for url field', () => {
977
+ const url = new URL('https://mcp.example.com/path');
978
+ new McpClient({ url });
979
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(url, undefined);
980
+ });
981
+ it('passes headers as requestInit to transport', () => {
982
+ new McpClient({ url: 'https://mcp.example.com', headers: { 'X-Api-Key': 'abc' } });
983
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL('https://mcp.example.com'), {
984
+ requestInit: { headers: { 'X-Api-Key': 'abc' } },
985
+ });
986
+ });
987
+ it('passes both auth and headers to transport', () => {
988
+ new McpClient({
989
+ url: 'https://mcp.example.com',
990
+ auth: { clientId: 'id', clientSecret: 'secret' },
991
+ headers: { 'X-Trace': '123' },
992
+ });
993
+ expect(ClientCredentialsProvider).toHaveBeenCalledWith({ clientId: 'id', clientSecret: 'secret' });
994
+ expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL('https://mcp.example.com'), {
995
+ authProvider: expect.anything(),
996
+ requestInit: { headers: { 'X-Trace': '123' } },
997
+ });
998
+ });
999
+ it('throws when headers is provided with transport', () => {
1000
+ expect(() => new McpClient({ transport: mockTransport, headers: { 'X-Foo': 'bar' } })).toThrow('"auth", "authProvider", and "headers" require "url"');
1001
+ });
1002
+ });
563
1003
  //# sourceMappingURL=mcp.test.js.map