@tambo-ai/react 0.65.3 → 0.66.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 (346) hide show
  1. package/README.md +120 -20
  2. package/dist/{providers/hoc → hoc}/with-tambo-interactable.d.ts +2 -2
  3. package/dist/hoc/with-tambo-interactable.d.ts.map +1 -0
  4. package/dist/{providers/hoc → hoc}/with-tambo-interactable.js +29 -2
  5. package/dist/hoc/with-tambo-interactable.js.map +1 -0
  6. package/dist/hoc/with-tambo-interactable.test.d.ts +2 -0
  7. package/dist/hoc/with-tambo-interactable.test.d.ts.map +1 -0
  8. package/dist/hoc/with-tambo-interactable.test.js +192 -0
  9. package/dist/hoc/with-tambo-interactable.test.js.map +1 -0
  10. package/dist/hooks/index.d.ts +1 -1
  11. package/dist/hooks/index.d.ts.map +1 -1
  12. package/dist/hooks/index.js +2 -1
  13. package/dist/hooks/index.js.map +1 -1
  14. package/dist/hooks/use-current-message.d.ts +51 -7
  15. package/dist/hooks/use-current-message.d.ts.map +1 -1
  16. package/dist/hooks/use-current-message.js +50 -6
  17. package/dist/hooks/use-current-message.js.map +1 -1
  18. package/dist/hooks/use-current-message.test.d.ts +2 -0
  19. package/dist/hooks/use-current-message.test.d.ts.map +1 -0
  20. package/dist/hooks/use-current-message.test.js +264 -0
  21. package/dist/hooks/use-current-message.test.js.map +1 -0
  22. package/dist/index.d.ts +10 -8
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/index.d.ts +1 -1
  27. package/dist/mcp/index.d.ts.map +1 -1
  28. package/dist/mcp/index.js +2 -1
  29. package/dist/mcp/index.js.map +1 -1
  30. package/dist/mcp/mcp-hooks.d.ts +77 -6
  31. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  32. package/dist/mcp/mcp-hooks.js +104 -40
  33. package/dist/mcp/mcp-hooks.js.map +1 -1
  34. package/dist/mcp/mcp-hooks.test.js +83 -18
  35. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  36. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  37. package/dist/mcp/tambo-mcp-provider.js +2 -1
  38. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  39. package/dist/model/component-metadata.d.ts +444 -14
  40. package/dist/model/component-metadata.d.ts.map +1 -1
  41. package/dist/model/component-metadata.js.map +1 -1
  42. package/dist/model/generate-component-response.d.ts +12 -1
  43. package/dist/model/generate-component-response.d.ts.map +1 -1
  44. package/dist/model/generate-component-response.js.map +1 -1
  45. package/dist/model/resource-info.d.ts +55 -0
  46. package/dist/model/resource-info.d.ts.map +1 -0
  47. package/dist/model/resource-info.js +3 -0
  48. package/dist/model/resource-info.js.map +1 -0
  49. package/dist/providers/index.d.ts +1 -1
  50. package/dist/providers/index.d.ts.map +1 -1
  51. package/dist/providers/index.js.map +1 -1
  52. package/dist/providers/tambo-component-provider.d.ts +4 -4
  53. package/dist/providers/tambo-component-provider.d.ts.map +1 -1
  54. package/dist/providers/tambo-component-provider.js.map +1 -1
  55. package/dist/providers/tambo-interactable-provider-partial-updates.test.js +87 -87
  56. package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  57. package/dist/providers/tambo-interactable-provider.d.ts +2 -3
  58. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  59. package/dist/providers/tambo-interactable-provider.js +47 -41
  60. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  61. package/dist/providers/tambo-interactables-additional-context-edge-cases.test.js +9 -9
  62. package/dist/providers/tambo-interactables-additional-context-edge-cases.test.js.map +1 -1
  63. package/dist/providers/tambo-interactables-additional-context.test.js +11 -11
  64. package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
  65. package/dist/providers/tambo-registry-provider.d.ts +28 -7
  66. package/dist/providers/tambo-registry-provider.d.ts.map +1 -1
  67. package/dist/providers/tambo-registry-provider.js +70 -181
  68. package/dist/providers/tambo-registry-provider.js.map +1 -1
  69. package/dist/providers/tambo-registry-provider.test.js +152 -30
  70. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  71. package/dist/providers/tambo-registry-schema-compat.test.d.ts +2 -0
  72. package/dist/providers/tambo-registry-schema-compat.test.d.ts.map +1 -0
  73. package/dist/providers/tambo-registry-schema-compat.test.js +616 -0
  74. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -0
  75. package/dist/providers/tambo-stubs.d.ts +2 -2
  76. package/dist/providers/tambo-stubs.d.ts.map +1 -1
  77. package/dist/providers/tambo-stubs.js +5 -0
  78. package/dist/providers/tambo-stubs.js.map +1 -1
  79. package/dist/providers/tambo-thread-input-provider.d.ts +1 -0
  80. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  81. package/dist/providers/tambo-thread-input-provider.js +3 -3
  82. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  83. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  84. package/dist/providers/tambo-thread-provider.js.map +1 -1
  85. package/dist/providers/tambo-thread-provider.test.js +32 -36
  86. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  87. package/dist/schema/index.d.ts +6 -0
  88. package/dist/schema/index.d.ts.map +1 -0
  89. package/dist/schema/index.js +18 -0
  90. package/dist/schema/index.js.map +1 -0
  91. package/dist/schema/json-schema.d.ts +35 -0
  92. package/dist/schema/json-schema.d.ts.map +1 -0
  93. package/dist/schema/json-schema.js +103 -0
  94. package/dist/schema/json-schema.js.map +1 -0
  95. package/dist/schema/schema.d.ts +66 -0
  96. package/dist/schema/schema.d.ts.map +1 -0
  97. package/dist/schema/schema.js +192 -0
  98. package/dist/schema/schema.js.map +1 -0
  99. package/dist/schema/schema.test.d.ts +2 -0
  100. package/dist/schema/schema.test.d.ts.map +1 -0
  101. package/dist/schema/schema.test.js +41 -0
  102. package/dist/schema/schema.test.js.map +1 -0
  103. package/dist/schema/standard-schema.d.ts +21 -0
  104. package/dist/schema/standard-schema.d.ts.map +1 -0
  105. package/dist/schema/standard-schema.js +37 -0
  106. package/dist/schema/standard-schema.js.map +1 -0
  107. package/dist/schema/validate.d.ts +14 -0
  108. package/dist/schema/validate.d.ts.map +1 -0
  109. package/dist/schema/validate.js +148 -0
  110. package/dist/schema/validate.js.map +1 -0
  111. package/dist/schema/validate.test.d.ts +2 -0
  112. package/dist/schema/validate.test.d.ts.map +1 -0
  113. package/dist/schema/validate.test.js +128 -0
  114. package/dist/schema/validate.test.js.map +1 -0
  115. package/dist/schema/zod.d.ts +54 -0
  116. package/dist/schema/zod.d.ts.map +1 -0
  117. package/dist/schema/zod.js +135 -0
  118. package/dist/schema/zod.js.map +1 -0
  119. package/dist/testing/tools.d.ts +29 -15
  120. package/dist/testing/tools.d.ts.map +1 -1
  121. package/dist/testing/tools.js +64 -19
  122. package/dist/testing/tools.js.map +1 -1
  123. package/dist/util/generate-component.d.ts.map +1 -1
  124. package/dist/util/generate-component.js +3 -3
  125. package/dist/util/generate-component.js.map +1 -1
  126. package/dist/util/mcp-server-utils.d.ts +23 -0
  127. package/dist/util/mcp-server-utils.d.ts.map +1 -0
  128. package/dist/util/mcp-server-utils.js +107 -0
  129. package/dist/util/mcp-server-utils.js.map +1 -0
  130. package/dist/util/mcp-server-utils.test.d.ts +2 -0
  131. package/dist/util/mcp-server-utils.test.d.ts.map +1 -0
  132. package/dist/util/mcp-server-utils.test.js +287 -0
  133. package/dist/util/mcp-server-utils.test.js.map +1 -0
  134. package/dist/util/message-builder.d.ts +2 -1
  135. package/dist/util/message-builder.d.ts.map +1 -1
  136. package/dist/util/message-builder.js +54 -36
  137. package/dist/util/message-builder.js.map +1 -1
  138. package/dist/util/message-builder.test.js +500 -13
  139. package/dist/util/message-builder.test.js.map +1 -1
  140. package/dist/util/registry-validators.d.ts +26 -0
  141. package/dist/util/registry-validators.d.ts.map +1 -0
  142. package/dist/util/registry-validators.js +105 -0
  143. package/dist/util/registry-validators.js.map +1 -0
  144. package/dist/util/registry-validators.test.d.ts +2 -0
  145. package/dist/util/registry-validators.test.d.ts.map +1 -0
  146. package/dist/util/registry-validators.test.js +235 -0
  147. package/dist/util/registry-validators.test.js.map +1 -0
  148. package/dist/util/registry.d.ts +35 -7
  149. package/dist/util/registry.d.ts.map +1 -1
  150. package/dist/util/registry.js +60 -77
  151. package/dist/util/registry.js.map +1 -1
  152. package/dist/util/registry.test.d.ts +2 -0
  153. package/dist/util/registry.test.d.ts.map +1 -0
  154. package/dist/util/registry.test.js +204 -0
  155. package/dist/util/registry.test.js.map +1 -0
  156. package/dist/util/resource-validators.d.ts +16 -0
  157. package/dist/util/resource-validators.d.ts.map +1 -0
  158. package/dist/util/resource-validators.js +34 -0
  159. package/dist/util/resource-validators.js.map +1 -0
  160. package/dist/util/tool-caller.d.ts +2 -2
  161. package/dist/util/tool-caller.d.ts.map +1 -1
  162. package/dist/util/tool-caller.js +12 -4
  163. package/dist/util/tool-caller.js.map +1 -1
  164. package/esm/{providers/hoc → hoc}/with-tambo-interactable.d.ts +2 -2
  165. package/esm/hoc/with-tambo-interactable.d.ts.map +1 -0
  166. package/esm/{providers/hoc → hoc}/with-tambo-interactable.js +29 -2
  167. package/esm/hoc/with-tambo-interactable.js.map +1 -0
  168. package/esm/hoc/with-tambo-interactable.test.d.ts +2 -0
  169. package/esm/hoc/with-tambo-interactable.test.d.ts.map +1 -0
  170. package/esm/hoc/with-tambo-interactable.test.js +187 -0
  171. package/esm/hoc/with-tambo-interactable.test.js.map +1 -0
  172. package/esm/hooks/index.d.ts +1 -1
  173. package/esm/hooks/index.d.ts.map +1 -1
  174. package/esm/hooks/index.js +1 -1
  175. package/esm/hooks/index.js.map +1 -1
  176. package/esm/hooks/use-current-message.d.ts +51 -7
  177. package/esm/hooks/use-current-message.d.ts.map +1 -1
  178. package/esm/hooks/use-current-message.js +48 -5
  179. package/esm/hooks/use-current-message.js.map +1 -1
  180. package/esm/hooks/use-current-message.test.d.ts +2 -0
  181. package/esm/hooks/use-current-message.test.d.ts.map +1 -0
  182. package/esm/hooks/use-current-message.test.js +259 -0
  183. package/esm/hooks/use-current-message.test.js.map +1 -0
  184. package/esm/index.d.ts +10 -8
  185. package/esm/index.d.ts.map +1 -1
  186. package/esm/index.js +4 -2
  187. package/esm/index.js.map +1 -1
  188. package/esm/mcp/index.d.ts +1 -1
  189. package/esm/mcp/index.d.ts.map +1 -1
  190. package/esm/mcp/index.js +1 -1
  191. package/esm/mcp/index.js.map +1 -1
  192. package/esm/mcp/mcp-hooks.d.ts +77 -6
  193. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  194. package/esm/mcp/mcp-hooks.js +103 -40
  195. package/esm/mcp/mcp-hooks.js.map +1 -1
  196. package/esm/mcp/mcp-hooks.test.js +84 -19
  197. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  198. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  199. package/esm/mcp/tambo-mcp-provider.js +2 -1
  200. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  201. package/esm/model/component-metadata.d.ts +444 -14
  202. package/esm/model/component-metadata.d.ts.map +1 -1
  203. package/esm/model/component-metadata.js.map +1 -1
  204. package/esm/model/generate-component-response.d.ts +12 -1
  205. package/esm/model/generate-component-response.d.ts.map +1 -1
  206. package/esm/model/generate-component-response.js.map +1 -1
  207. package/esm/model/resource-info.d.ts +55 -0
  208. package/esm/model/resource-info.d.ts.map +1 -0
  209. package/esm/model/resource-info.js +2 -0
  210. package/esm/model/resource-info.js.map +1 -0
  211. package/esm/providers/index.d.ts +1 -1
  212. package/esm/providers/index.d.ts.map +1 -1
  213. package/esm/providers/index.js.map +1 -1
  214. package/esm/providers/tambo-component-provider.d.ts +4 -4
  215. package/esm/providers/tambo-component-provider.d.ts.map +1 -1
  216. package/esm/providers/tambo-component-provider.js.map +1 -1
  217. package/esm/providers/tambo-interactable-provider-partial-updates.test.js +1 -1
  218. package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  219. package/esm/providers/tambo-interactable-provider.d.ts +2 -3
  220. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  221. package/esm/providers/tambo-interactable-provider.js +44 -38
  222. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  223. package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js +3 -3
  224. package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js.map +1 -1
  225. package/esm/providers/tambo-interactables-additional-context.test.js +3 -3
  226. package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
  227. package/esm/providers/tambo-registry-provider.d.ts +28 -7
  228. package/esm/providers/tambo-registry-provider.d.ts.map +1 -1
  229. package/esm/providers/tambo-registry-provider.js +67 -175
  230. package/esm/providers/tambo-registry-provider.js.map +1 -1
  231. package/esm/providers/tambo-registry-provider.test.js +148 -26
  232. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  233. package/esm/providers/tambo-registry-schema-compat.test.d.ts +2 -0
  234. package/esm/providers/tambo-registry-schema-compat.test.d.ts.map +1 -0
  235. package/esm/providers/tambo-registry-schema-compat.test.js +578 -0
  236. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -0
  237. package/esm/providers/tambo-stubs.d.ts +2 -2
  238. package/esm/providers/tambo-stubs.d.ts.map +1 -1
  239. package/esm/providers/tambo-stubs.js +5 -0
  240. package/esm/providers/tambo-stubs.js.map +1 -1
  241. package/esm/providers/tambo-thread-input-provider.d.ts +1 -0
  242. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  243. package/esm/providers/tambo-thread-input-provider.js +3 -3
  244. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  245. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  246. package/esm/providers/tambo-thread-provider.js.map +1 -1
  247. package/esm/providers/tambo-thread-provider.test.js +24 -28
  248. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  249. package/esm/schema/index.d.ts +6 -0
  250. package/esm/schema/index.d.ts.map +1 -0
  251. package/esm/schema/index.js +6 -0
  252. package/esm/schema/index.js.map +1 -0
  253. package/esm/schema/json-schema.d.ts +35 -0
  254. package/esm/schema/json-schema.d.ts.map +1 -0
  255. package/esm/schema/json-schema.js +98 -0
  256. package/esm/schema/json-schema.js.map +1 -0
  257. package/esm/schema/schema.d.ts +66 -0
  258. package/esm/schema/schema.d.ts.map +1 -0
  259. package/esm/schema/schema.js +185 -0
  260. package/esm/schema/schema.js.map +1 -0
  261. package/esm/schema/schema.test.d.ts +2 -0
  262. package/esm/schema/schema.test.d.ts.map +1 -0
  263. package/esm/schema/schema.test.js +39 -0
  264. package/esm/schema/schema.test.js.map +1 -0
  265. package/esm/schema/standard-schema.d.ts +21 -0
  266. package/esm/schema/standard-schema.d.ts.map +1 -0
  267. package/esm/schema/standard-schema.js +34 -0
  268. package/esm/schema/standard-schema.js.map +1 -0
  269. package/esm/schema/validate.d.ts +14 -0
  270. package/esm/schema/validate.d.ts.map +1 -0
  271. package/esm/schema/validate.js +145 -0
  272. package/esm/schema/validate.js.map +1 -0
  273. package/esm/schema/validate.test.d.ts +2 -0
  274. package/esm/schema/validate.test.d.ts.map +1 -0
  275. package/esm/schema/validate.test.js +126 -0
  276. package/esm/schema/validate.test.js.map +1 -0
  277. package/esm/schema/zod.d.ts +54 -0
  278. package/esm/schema/zod.d.ts.map +1 -0
  279. package/esm/schema/zod.js +124 -0
  280. package/esm/schema/zod.js.map +1 -0
  281. package/esm/testing/tools.d.ts +29 -15
  282. package/esm/testing/tools.d.ts.map +1 -1
  283. package/esm/testing/tools.js +62 -16
  284. package/esm/testing/tools.js.map +1 -1
  285. package/esm/util/generate-component.d.ts.map +1 -1
  286. package/esm/util/generate-component.js +3 -3
  287. package/esm/util/generate-component.js.map +1 -1
  288. package/esm/util/mcp-server-utils.d.ts +23 -0
  289. package/esm/util/mcp-server-utils.d.ts.map +1 -0
  290. package/esm/util/mcp-server-utils.js +102 -0
  291. package/esm/util/mcp-server-utils.js.map +1 -0
  292. package/esm/util/mcp-server-utils.test.d.ts +2 -0
  293. package/esm/util/mcp-server-utils.test.d.ts.map +1 -0
  294. package/esm/util/mcp-server-utils.test.js +285 -0
  295. package/esm/util/mcp-server-utils.test.js.map +1 -0
  296. package/esm/util/message-builder.d.ts +2 -1
  297. package/esm/util/message-builder.d.ts.map +1 -1
  298. package/esm/util/message-builder.js +54 -36
  299. package/esm/util/message-builder.js.map +1 -1
  300. package/esm/util/message-builder.test.js +500 -13
  301. package/esm/util/message-builder.test.js.map +1 -1
  302. package/esm/util/registry-validators.d.ts +26 -0
  303. package/esm/util/registry-validators.d.ts.map +1 -0
  304. package/esm/util/registry-validators.js +100 -0
  305. package/esm/util/registry-validators.js.map +1 -0
  306. package/esm/util/registry-validators.test.d.ts +2 -0
  307. package/esm/util/registry-validators.test.d.ts.map +1 -0
  308. package/esm/util/registry-validators.test.js +233 -0
  309. package/esm/util/registry-validators.test.js.map +1 -0
  310. package/esm/util/registry.d.ts +35 -7
  311. package/esm/util/registry.d.ts.map +1 -1
  312. package/esm/util/registry.js +57 -73
  313. package/esm/util/registry.js.map +1 -1
  314. package/esm/util/registry.test.d.ts +2 -0
  315. package/esm/util/registry.test.d.ts.map +1 -0
  316. package/esm/util/registry.test.js +169 -0
  317. package/esm/util/registry.test.js.map +1 -0
  318. package/esm/util/resource-validators.d.ts +16 -0
  319. package/esm/util/resource-validators.d.ts.map +1 -0
  320. package/esm/util/resource-validators.js +30 -0
  321. package/esm/util/resource-validators.js.map +1 -0
  322. package/esm/util/tool-caller.d.ts +2 -2
  323. package/esm/util/tool-caller.d.ts.map +1 -1
  324. package/esm/util/tool-caller.js +12 -4
  325. package/esm/util/tool-caller.js.map +1 -1
  326. package/package.json +13 -8
  327. package/dist/providers/hoc/with-tambo-interactable.d.ts.map +0 -1
  328. package/dist/providers/hoc/with-tambo-interactable.js.map +0 -1
  329. package/dist/util/validate-zod-schema.d.ts +0 -10
  330. package/dist/util/validate-zod-schema.d.ts.map +0 -1
  331. package/dist/util/validate-zod-schema.js +0 -100
  332. package/dist/util/validate-zod-schema.js.map +0 -1
  333. package/dist/util/validate-zod-schema.test.d.ts +0 -2
  334. package/dist/util/validate-zod-schema.test.d.ts.map +0 -1
  335. package/dist/util/validate-zod-schema.test.js +0 -96
  336. package/dist/util/validate-zod-schema.test.js.map +0 -1
  337. package/esm/providers/hoc/with-tambo-interactable.d.ts.map +0 -1
  338. package/esm/providers/hoc/with-tambo-interactable.js.map +0 -1
  339. package/esm/util/validate-zod-schema.d.ts +0 -10
  340. package/esm/util/validate-zod-schema.d.ts.map +0 -1
  341. package/esm/util/validate-zod-schema.js +0 -97
  342. package/esm/util/validate-zod-schema.js.map +0 -1
  343. package/esm/util/validate-zod-schema.test.d.ts +0 -2
  344. package/esm/util/validate-zod-schema.test.d.ts.map +0 -1
  345. package/esm/util/validate-zod-schema.test.js +0 -94
  346. package/esm/util/validate-zod-schema.test.js.map +0 -1
@@ -1,25 +1,71 @@
1
- import zodToJsonSchema from "zod-to-json-schema";
1
+ import { isStandardSchema, safeSchemaToJsonSchema } from "../schema";
2
+ import { adaptToolFromFnSchema, mapTamboToolToContextTool, } from "../util/registry";
2
3
  /**
3
- * Serializes the registry for testing purposes
4
+ * Serializes the registry for testing purposes.
5
+ * Converts Standard Schema validators to JSON Schema format.
6
+ * Uses the same logic as production code via mapTamboToolToContextTool.
4
7
  * @param mockRegistry - The registry to serialize
5
- * @returns The serialized registry
8
+ * @returns The serialized registry with JSON Schema representations
6
9
  */
7
10
  export function serializeRegistry(mockRegistry) {
8
11
  return mockRegistry.map(({ component: _component, propsSchema, associatedTools, ...componentEntry }) => ({
9
12
  ...componentEntry,
10
- props: zodToJsonSchema(propsSchema),
11
- contextTools: associatedTools?.map(({ toolSchema, tool: _tool, ...toolEntry }) => ({
12
- ...toolEntry,
13
- parameters: toolSchema
14
- .parameters()
15
- .items.map((p, index) => ({
16
- name: `param${index + 1}`,
17
- schema: zodToJsonSchema(p),
18
- isRequired: true,
19
- description: p.description,
20
- type: zodToJsonSchema(p).type,
21
- })),
22
- })),
13
+ props: isStandardSchema(propsSchema)
14
+ ? safeSchemaToJsonSchema(propsSchema)
15
+ : propsSchema,
16
+ contextTools: associatedTools?.map((tool) => mapTamboToolToContextTool(adaptToolFromFnSchema(tool))),
23
17
  }));
24
18
  }
19
+ // Helper to create a minimal TamboTool for testing
20
+ /**
21
+ * Creates a mock TamboTool with the given input schema for testing purposes.
22
+ * Accepts either an inputSchema directly or an options object with inputSchema and outputSchema.
23
+ * @param schemaOrOptions - The input schema or options object
24
+ * @returns A mock TamboTool instance
25
+ * @internal
26
+ */
27
+ export function createMockTool(schemaOrOptions) {
28
+ // Check if options object was passed
29
+ const hasInputSchemaProperty = (obj) => {
30
+ return (typeof obj === "object" &&
31
+ obj !== null &&
32
+ "inputSchema" in obj &&
33
+ obj.inputSchema != null);
34
+ };
35
+ if (hasInputSchemaProperty(schemaOrOptions)) {
36
+ return {
37
+ name: "mockTool",
38
+ description: "A mock tool for testing",
39
+ tool: jest.fn().mockImplementation(() => { }),
40
+ inputSchema: schemaOrOptions.inputSchema,
41
+ outputSchema: schemaOrOptions.outputSchema ?? {
42
+ type: "object",
43
+ properties: {},
44
+ },
45
+ };
46
+ }
47
+ return {
48
+ name: "mockTool",
49
+ description: "A mock tool for testing",
50
+ tool: jest.fn().mockImplementation(() => { }),
51
+ inputSchema: schemaOrOptions,
52
+ outputSchema: { type: "object", properties: {} },
53
+ };
54
+ }
55
+ // Helper to create a minimal TamboTool for testing -- uses toolSchema field as function instead of inputSchema
56
+ /**
57
+ * Creates a mock TamboToolWithToolSchema for testing purposes.
58
+ * Does NOT adapt to inputSchema format - preserves the deprecated toolSchema interface.
59
+ * @param toolSchema - The tool schema for the tool
60
+ * @returns A mock TamboToolWithToolSchema instance (NOT adapted)
61
+ * @internal
62
+ */
63
+ export function createMockToolWithToolSchema(toolSchema) {
64
+ return {
65
+ name: "testTool",
66
+ description: "A test tool",
67
+ tool: jest.fn().mockImplementation(() => { }),
68
+ toolSchema,
69
+ };
70
+ }
25
71
  //# sourceMappingURL=tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/testing/tools.ts"],"names":[],"mappings":"AACA,OAAO,eAAe,MAAM,oBAAoB,CAAC;AAGjD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAA8B;IAC9D,OAAO,YAAY,CAAC,GAAG,CACrB,CAAC,EACC,SAAS,EAAE,UAAU,EACrB,WAAW,EACX,eAAe,EACf,GAAG,cAAc,EAClB,EAAE,EAAE,CAAC,CAAC;QACL,GAAG,cAAc;QACjB,KAAK,EAAE,eAAe,CAAC,WAA2B,CAAC;QACnD,YAAY,EAAE,eAAe,EAAE,GAAG,CAChC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9C,GAAG,SAAS;YACZ,UAAU,EAAG,UAAsC;iBAChD,UAAU,EAAE;iBACZ,KAAK,CAAC,GAAG,CAAC,CAAC,CAAe,EAAE,KAAa,EAAE,EAAE,CAAC,CAAC;gBAC9C,IAAI,EAAE,QAAQ,KAAK,GAAG,CAAC,EAAE;gBACzB,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;gBAC1B,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,IAAI,EAAG,eAAe,CAAC,CAAC,CAAS,CAAC,IAAI;aACvC,CAAC,CAAC;SACN,CAAC,CACH;KACF,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["import { z } from \"zod/v3\";\nimport zodToJsonSchema from \"zod-to-json-schema\";\nimport { TamboComponent } from \"../providers\";\n\n/**\n * Serializes the registry for testing purposes\n * @param mockRegistry - The registry to serialize\n * @returns The serialized registry\n */\nexport function serializeRegistry(mockRegistry: TamboComponent[]) {\n return mockRegistry.map(\n ({\n component: _component,\n propsSchema,\n associatedTools,\n ...componentEntry\n }) => ({\n ...componentEntry,\n props: zodToJsonSchema(propsSchema as z.ZodTypeAny),\n contextTools: associatedTools?.map(\n ({ toolSchema, tool: _tool, ...toolEntry }) => ({\n ...toolEntry,\n parameters: (toolSchema as z.ZodFunction<any, any>)\n .parameters()\n .items.map((p: z.ZodTypeAny, index: number) => ({\n name: `param${index + 1}`,\n schema: zodToJsonSchema(p),\n isRequired: true,\n description: p.description,\n type: (zodToJsonSchema(p) as any).type,\n })),\n }),\n ),\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/testing/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAE1B;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAA8B;IAC9D,OAAO,YAAY,CAAC,GAAG,CACrB,CAAC,EACC,SAAS,EAAE,UAAU,EACrB,WAAW,EACX,eAAe,EACf,GAAG,cAAc,EAClB,EAAE,EAAE,CAAC,CAAC;QACL,GAAG,cAAc;QACjB,KAAK,EAAE,gBAAgB,CAAC,WAAW,CAAC;YAClC,CAAC,CAAC,sBAAsB,CAAC,WAAW,CAAC;YACrC,CAAC,CAAC,WAAW;QACf,YAAY,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1C,yBAAyB,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CACvD;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAOD,mDAAmD;AACnD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,eAAiE;IAEjE,qCAAqC;IACrC,MAAM,sBAAsB,GAAG,CAC7B,GAAY,EACkB,EAAE;QAChC,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;YACvB,GAAG,KAAK,IAAI;YACZ,aAAa,IAAI,GAAG;YACpB,GAAG,CAAC,WAAW,IAAI,IAAI,CACxB,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,sBAAsB,CAAC,eAAe,CAAC,EAAE,CAAC;QAC5C,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,yBAAyB;YACtC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;YAC5C,WAAW,EAAE,eAAe,CAAC,WAAW;YACxC,YAAY,EAAE,eAAe,CAAC,YAAY,IAAI;gBAC5C,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE;aACf;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,yBAAyB;QACtC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAC5C,WAAW,EAAE,eAAe;QAC5B,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KACjD,CAAC;AACJ,CAAC;AAED,+GAA+G;AAC/G;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAAiD;IAEjD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,aAAa;QAC1B,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAC5C,UAAU;KACX,CAAC;AACJ,CAAC","sourcesContent":["import { TamboToolWithToolSchema } from \"../model/component-metadata\";\nimport { TamboComponent, TamboTool } from \"../providers\";\nimport { isStandardSchema, safeSchemaToJsonSchema } from \"../schema\";\nimport {\n adaptToolFromFnSchema,\n mapTamboToolToContextTool,\n} from \"../util/registry\";\n\n/**\n * Serializes the registry for testing purposes.\n * Converts Standard Schema validators to JSON Schema format.\n * Uses the same logic as production code via mapTamboToolToContextTool.\n * @param mockRegistry - The registry to serialize\n * @returns The serialized registry with JSON Schema representations\n */\nexport function serializeRegistry(mockRegistry: TamboComponent[]) {\n return mockRegistry.map(\n ({\n component: _component,\n propsSchema,\n associatedTools,\n ...componentEntry\n }) => ({\n ...componentEntry,\n props: isStandardSchema(propsSchema)\n ? safeSchemaToJsonSchema(propsSchema)\n : propsSchema,\n contextTools: associatedTools?.map((tool) =>\n mapTamboToolToContextTool(adaptToolFromFnSchema(tool)),\n ),\n }),\n );\n}\n\ninterface CreateMockToolOptions {\n inputSchema: TamboTool[\"inputSchema\"];\n outputSchema?: TamboTool[\"outputSchema\"];\n}\n\n// Helper to create a minimal TamboTool for testing\n/**\n * Creates a mock TamboTool with the given input schema for testing purposes.\n * Accepts either an inputSchema directly or an options object with inputSchema and outputSchema.\n * @param schemaOrOptions - The input schema or options object\n * @returns A mock TamboTool instance\n * @internal\n */\nexport function createMockTool(\n schemaOrOptions: TamboTool[\"inputSchema\"] | CreateMockToolOptions,\n): TamboTool {\n // Check if options object was passed\n const hasInputSchemaProperty = (\n obj: unknown,\n ): obj is CreateMockToolOptions => {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"inputSchema\" in obj &&\n obj.inputSchema != null\n );\n };\n\n if (hasInputSchemaProperty(schemaOrOptions)) {\n return {\n name: \"mockTool\",\n description: \"A mock tool for testing\",\n tool: jest.fn().mockImplementation(() => {}),\n inputSchema: schemaOrOptions.inputSchema,\n outputSchema: schemaOrOptions.outputSchema ?? {\n type: \"object\",\n properties: {},\n },\n };\n }\n\n return {\n name: \"mockTool\",\n description: \"A mock tool for testing\",\n tool: jest.fn().mockImplementation(() => {}),\n inputSchema: schemaOrOptions,\n outputSchema: { type: \"object\", properties: {} },\n };\n}\n\n// Helper to create a minimal TamboTool for testing -- uses toolSchema field as function instead of inputSchema\n/**\n * Creates a mock TamboToolWithToolSchema for testing purposes.\n * Does NOT adapt to inputSchema format - preserves the deprecated toolSchema interface.\n * @param toolSchema - The tool schema for the tool\n * @returns A mock TamboToolWithToolSchema instance (NOT adapted)\n * @internal\n */\nexport function createMockToolWithToolSchema(\n toolSchema: TamboToolWithToolSchema[\"toolSchema\"],\n): TamboToolWithToolSchema {\n return {\n name: \"testTool\",\n description: \"A test tool\",\n tool: jest.fn().mockImplementation(() => {}),\n toolSchema,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-component.d.ts","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,0BAA0B,CAAC;AAK/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG1E;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAC3C,aAAa,EAAE,iBAAiB,GAC/B,kBAAkB,CAsCpB"}
1
+ {"version":3,"file":"generate-component.d.ts","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,0BAA0B,CAAC;AAI/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAI1E;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAC3C,aAAa,EAAE,iBAAiB,GAC/B,kBAAkB,CAqCpB"}
@@ -1,7 +1,7 @@
1
1
  import { parse } from "partial-json";
2
2
  import React from "react";
3
- import { z } from "zod/v3";
4
3
  import { wrapWithTamboMessageProvider } from "../hooks/use-current-message";
4
+ import { isStandardSchema } from "../schema";
5
5
  import { getComponentFromRegistry } from "../util/registry";
6
6
  /**
7
7
  * Generate a message that has a component rendered into it, if the message
@@ -16,8 +16,8 @@ export function renderComponentIntoMessage(message, componentList) {
16
16
  }
17
17
  const parsedProps = parse(JSON.stringify(message.component.props));
18
18
  const registeredComponent = getComponentFromRegistry(message.component.componentName, componentList);
19
- const validatedProps = registeredComponent.props instanceof z.ZodType
20
- ? registeredComponent.props.parse(parsedProps)
19
+ const validatedProps = isStandardSchema(registeredComponent.props)
20
+ ? registeredComponent.props["~standard"].validate(parsedProps)
21
21
  : parsedProps;
22
22
  const renderedComponent = React.createElement(registeredComponent.component, validatedProps);
23
23
  // Create the full message object first so we can pass it to the provider
@@ -1 +1 @@
1
- {"version":3,"file":"generate-component.js","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAG5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA2C,EAC3C,aAAgC;IAEhC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,MAAM,mBAAmB,GAAG,wBAAwB,CAClD,OAAO,CAAC,SAAS,CAAC,aAAa,EAC/B,aAAa,CACd,CAAC;IAEF,MAAM,cAAc,GAClB,mBAAmB,CAAC,KAAK,YAAY,CAAC,CAAC,OAAO;QAC5C,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC;QAC9C,CAAC,CAAC,WAAW,CAAC;IAElB,MAAM,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAC3C,mBAAmB,CAAC,SAAS,EAC7B,cAAc,CACf,CAAC;IAEF,yEAAyE;IACzE,MAAM,WAAW,GAAuB;QACtC,GAAG,OAAO;QACV,SAAS,EAAE;YACT,GAAG,OAAO,CAAC,SAAS;YACpB,KAAK,EAAE,cAAc;SACtB;KACF,CAAC;IAEF,MAAM,gBAAgB,GAAG,4BAA4B,CACnD,iBAAiB,EACjB,WAAW,CACZ,CAAC;IAEF,OAAO;QACL,GAAG,WAAW;QACd,iBAAiB,EAAE,gBAAgB;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { parse } from \"partial-json\";\nimport React from \"react\";\nimport { z } from \"zod/v3\";\nimport { wrapWithTamboMessageProvider } from \"../hooks/use-current-message\";\nimport { ComponentRegistry } from \"../model/component-metadata\";\nimport { TamboThreadMessage } from \"../model/generate-component-response\";\nimport { getComponentFromRegistry } from \"../util/registry\";\n\n/**\n * Generate a message that has a component rendered into it, if the message\n * came with one.\n * @param message - The message that may contain a component\n * @param componentList - the list of available components\n * @returns The updated message with the component rendered into it\n */\nexport function renderComponentIntoMessage(\n message: TamboAI.Beta.Threads.ThreadMessage,\n componentList: ComponentRegistry,\n): TamboThreadMessage {\n if (!message.component?.componentName) {\n throw new Error(\"Component not found\");\n }\n const parsedProps = parse(JSON.stringify(message.component.props));\n const registeredComponent = getComponentFromRegistry(\n message.component.componentName,\n componentList,\n );\n\n const validatedProps =\n registeredComponent.props instanceof z.ZodType\n ? registeredComponent.props.parse(parsedProps)\n : parsedProps;\n\n const renderedComponent = React.createElement(\n registeredComponent.component,\n validatedProps,\n );\n\n // Create the full message object first so we can pass it to the provider\n const fullMessage: TamboThreadMessage = {\n ...message,\n component: {\n ...message.component,\n props: validatedProps,\n },\n };\n\n const wrappedComponent = wrapWithTamboMessageProvider(\n renderedComponent,\n fullMessage,\n );\n\n return {\n ...fullMessage,\n renderedComponent: wrappedComponent,\n };\n}\n"]}
1
+ {"version":3,"file":"generate-component.js","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAG5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA2C,EAC3C,aAAgC;IAEhC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,MAAM,mBAAmB,GAAG,wBAAwB,CAClD,OAAO,CAAC,SAAS,CAAC,aAAa,EAC/B,aAAa,CACd,CAAC;IAEF,MAAM,cAAc,GAAG,gBAAgB,CAAC,mBAAmB,CAAC,KAAK,CAAC;QAChE,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QAC9D,CAAC,CAAC,WAAW,CAAC;IAEhB,MAAM,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAC3C,mBAAmB,CAAC,SAAS,EAC7B,cAAc,CACf,CAAC;IAEF,yEAAyE;IACzE,MAAM,WAAW,GAAuB;QACtC,GAAG,OAAO;QACV,SAAS,EAAE;YACT,GAAG,OAAO,CAAC,SAAS;YACpB,KAAK,EAAE,cAAc;SACtB;KACF,CAAC;IAEF,MAAM,gBAAgB,GAAG,4BAA4B,CACnD,iBAAiB,EACjB,WAAW,CACZ,CAAC;IAEF,OAAO;QACL,GAAG,WAAW;QACd,iBAAiB,EAAE,gBAAgB;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { parse } from \"partial-json\";\nimport React from \"react\";\nimport { wrapWithTamboMessageProvider } from \"../hooks/use-current-message\";\nimport { ComponentRegistry } from \"../model/component-metadata\";\nimport { TamboThreadMessage } from \"../model/generate-component-response\";\nimport { isStandardSchema } from \"../schema\";\nimport { getComponentFromRegistry } from \"../util/registry\";\n\n/**\n * Generate a message that has a component rendered into it, if the message\n * came with one.\n * @param message - The message that may contain a component\n * @param componentList - the list of available components\n * @returns The updated message with the component rendered into it\n */\nexport function renderComponentIntoMessage(\n message: TamboAI.Beta.Threads.ThreadMessage,\n componentList: ComponentRegistry,\n): TamboThreadMessage {\n if (!message.component?.componentName) {\n throw new Error(\"Component not found\");\n }\n const parsedProps = parse(JSON.stringify(message.component.props));\n const registeredComponent = getComponentFromRegistry(\n message.component.componentName,\n componentList,\n );\n\n const validatedProps = isStandardSchema(registeredComponent.props)\n ? registeredComponent.props[\"~standard\"].validate(parsedProps)\n : parsedProps;\n\n const renderedComponent = React.createElement(\n registeredComponent.component,\n validatedProps,\n );\n\n // Create the full message object first so we can pass it to the provider\n const fullMessage: TamboThreadMessage = {\n ...message,\n component: {\n ...message.component,\n props: validatedProps,\n },\n };\n\n const wrappedComponent = wrapWithTamboMessageProvider(\n renderedComponent,\n fullMessage,\n );\n\n return {\n ...fullMessage,\n renderedComponent: wrappedComponent,\n };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { McpServerInfo, NormalizedMcpServerInfo } from "../model/mcp-server-info";
2
+ /**
3
+ * Derives a short, meaningful key from a server URL.
4
+ * Strips TLDs and common prefixes to get a human-readable identifier.
5
+ * For example, "https://mcp.linear.app/mcp" becomes "linear".
6
+ * @returns A lowercased, human-readable key derived from the URL
7
+ */
8
+ export declare function deriveServerKey(url: string): string;
9
+ /**
10
+ * Normalizes an MCP server info object, ensuring it has a serverKey.
11
+ * If serverKey is not provided, derives it from the URL.
12
+ * @returns The normalized MCP server info object
13
+ */
14
+ export declare function normalizeServerInfo(server: McpServerInfo | string): NormalizedMcpServerInfo;
15
+ /**
16
+ * Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.
17
+ * First deduplicates by connection (url + transport), then ensures serverKey uniqueness
18
+ * by appending -2, -3, etc. to duplicate serverKeys.
19
+ * @param servers - Array of normalized MCP server info objects
20
+ * @returns Array of deduplicated servers with unique serverKeys
21
+ */
22
+ export declare function deduplicateMcpServers(servers: NormalizedMcpServerInfo[]): NormalizedMcpServerInfo[];
23
+ //# sourceMappingURL=mcp-server-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server-utils.d.ts","sourceRoot":"","sources":["../../src/util/mcp-server-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAGlC;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmDnD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,GAAG,MAAM,GAC7B,uBAAuB,CAazB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,uBAAuB,EAAE,GACjC,uBAAuB,EAAE,CA8B3B"}
@@ -0,0 +1,102 @@
1
+ import { getMcpServerUniqueKey, MCPTransport } from "../model/mcp-server-info";
2
+ /**
3
+ * Derives a short, meaningful key from a server URL.
4
+ * Strips TLDs and common prefixes to get a human-readable identifier.
5
+ * For example, "https://mcp.linear.app/mcp" becomes "linear".
6
+ * @returns A lowercased, human-readable key derived from the URL
7
+ */
8
+ export function deriveServerKey(url) {
9
+ try {
10
+ const parsed = new URL(url);
11
+ const hostname = parsed.hostname;
12
+ // Split hostname into parts
13
+ const parts = hostname.split(".");
14
+ // Remove common TLD patterns
15
+ // Handle cases like: .com, .org, .co.uk, .com.au, etc.
16
+ let relevantParts = [...parts];
17
+ // If we have 3+ parts and the last two are short (likely TLD like .co.uk)
18
+ if (relevantParts.length >= 3 &&
19
+ relevantParts[relevantParts.length - 1].length <= 3 &&
20
+ relevantParts[relevantParts.length - 2].length <= 3) {
21
+ relevantParts = relevantParts.slice(0, -2);
22
+ }
23
+ // Otherwise just remove the last part (TLD like .com)
24
+ else if (relevantParts.length >= 2) {
25
+ relevantParts = relevantParts.slice(0, -1);
26
+ }
27
+ // From what's left, prefer the rightmost part that's not a common prefix
28
+ // Common prefixes: www, api, mcp, app, etc.
29
+ const commonPrefixes = new Set([
30
+ "www",
31
+ "api",
32
+ "mcp",
33
+ "app",
34
+ "staging",
35
+ "dev",
36
+ "prod",
37
+ ]);
38
+ // Work backwards through the parts to find a meaningful name
39
+ for (let i = relevantParts.length - 1; i >= 0; i--) {
40
+ const part = relevantParts[i];
41
+ if (part && !commonPrefixes.has(part.toLowerCase())) {
42
+ return part.toLowerCase();
43
+ }
44
+ }
45
+ // Fallback: use the last relevant part even if it's a common prefix
46
+ return relevantParts[relevantParts.length - 1]?.toLowerCase() || hostname;
47
+ }
48
+ catch {
49
+ // If URL parsing fails, just return a sanitized version of the input
50
+ return url.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
51
+ }
52
+ }
53
+ /**
54
+ * Normalizes an MCP server info object, ensuring it has a serverKey.
55
+ * If serverKey is not provided, derives it from the URL.
56
+ * @returns The normalized MCP server info object
57
+ */
58
+ export function normalizeServerInfo(server) {
59
+ const base = typeof server === "string"
60
+ ? {
61
+ url: server,
62
+ transport: MCPTransport.HTTP,
63
+ }
64
+ : server;
65
+ const serverKey = base.serverKey ?? deriveServerKey(base.url);
66
+ const transport = base.transport ?? MCPTransport.HTTP;
67
+ return { ...base, transport, serverKey };
68
+ }
69
+ /**
70
+ * Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.
71
+ * First deduplicates by connection (url + transport), then ensures serverKey uniqueness
72
+ * by appending -2, -3, etc. to duplicate serverKeys.
73
+ * @param servers - Array of normalized MCP server info objects
74
+ * @returns Array of deduplicated servers with unique serverKeys
75
+ */
76
+ export function deduplicateMcpServers(servers) {
77
+ if (servers.length === 0) {
78
+ return servers;
79
+ }
80
+ // 1. Deduplicate by connection identity using a stable key
81
+ const byKey = new Map();
82
+ for (const server of servers) {
83
+ const key = getMcpServerUniqueKey(server);
84
+ byKey.set(key, server);
85
+ }
86
+ const deduped = Array.from(byKey.values());
87
+ // 2. Ensure serverKey uniqueness for readable, unambiguous prefixes
88
+ const seen = new Map();
89
+ return deduped.map((server) => {
90
+ const baseKey = server.serverKey;
91
+ const count = (seen.get(baseKey) ?? 0) + 1;
92
+ seen.set(baseKey, count);
93
+ if (count === 1) {
94
+ return server;
95
+ }
96
+ return {
97
+ ...server,
98
+ serverKey: `${baseKey}-${count}`,
99
+ };
100
+ });
101
+ }
102
+ //# sourceMappingURL=mcp-server-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server-utils.js","sourceRoot":"","sources":["../../src/util/mcp-server-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n McpServerInfo,\n NormalizedMcpServerInfo,\n} from \"../model/mcp-server-info\";\nimport { getMcpServerUniqueKey, MCPTransport } from \"../model/mcp-server-info\";\n\n/**\n * Derives a short, meaningful key from a server URL.\n * Strips TLDs and common prefixes to get a human-readable identifier.\n * For example, \"https://mcp.linear.app/mcp\" becomes \"linear\".\n * @returns A lowercased, human-readable key derived from the URL\n */\nexport function deriveServerKey(url: string): string {\n try {\n const parsed = new URL(url);\n const hostname = parsed.hostname;\n\n // Split hostname into parts\n const parts = hostname.split(\".\");\n\n // Remove common TLD patterns\n // Handle cases like: .com, .org, .co.uk, .com.au, etc.\n let relevantParts = [...parts];\n\n // If we have 3+ parts and the last two are short (likely TLD like .co.uk)\n if (\n relevantParts.length >= 3 &&\n relevantParts[relevantParts.length - 1].length <= 3 &&\n relevantParts[relevantParts.length - 2].length <= 3\n ) {\n relevantParts = relevantParts.slice(0, -2);\n }\n // Otherwise just remove the last part (TLD like .com)\n else if (relevantParts.length >= 2) {\n relevantParts = relevantParts.slice(0, -1);\n }\n\n // From what's left, prefer the rightmost part that's not a common prefix\n // Common prefixes: www, api, mcp, app, etc.\n const commonPrefixes = new Set([\n \"www\",\n \"api\",\n \"mcp\",\n \"app\",\n \"staging\",\n \"dev\",\n \"prod\",\n ]);\n\n // Work backwards through the parts to find a meaningful name\n for (let i = relevantParts.length - 1; i >= 0; i--) {\n const part = relevantParts[i];\n if (part && !commonPrefixes.has(part.toLowerCase())) {\n return part.toLowerCase();\n }\n }\n\n // Fallback: use the last relevant part even if it's a common prefix\n return relevantParts[relevantParts.length - 1]?.toLowerCase() || hostname;\n } catch {\n // If URL parsing fails, just return a sanitized version of the input\n return url.replace(/[^a-zA-Z0-9]/g, \"_\").toLowerCase();\n }\n}\n\n/**\n * Normalizes an MCP server info object, ensuring it has a serverKey.\n * If serverKey is not provided, derives it from the URL.\n * @returns The normalized MCP server info object\n */\nexport function normalizeServerInfo(\n server: McpServerInfo | string,\n): NormalizedMcpServerInfo {\n const base: McpServerInfo =\n typeof server === \"string\"\n ? {\n url: server,\n transport: MCPTransport.HTTP,\n }\n : server;\n\n const serverKey = base.serverKey ?? deriveServerKey(base.url);\n const transport = base.transport ?? MCPTransport.HTTP;\n\n return { ...base, transport, serverKey };\n}\n\n/**\n * Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.\n * First deduplicates by connection (url + transport), then ensures serverKey uniqueness\n * by appending -2, -3, etc. to duplicate serverKeys.\n * @param servers - Array of normalized MCP server info objects\n * @returns Array of deduplicated servers with unique serverKeys\n */\nexport function deduplicateMcpServers(\n servers: NormalizedMcpServerInfo[],\n): NormalizedMcpServerInfo[] {\n if (servers.length === 0) {\n return servers;\n }\n\n // 1. Deduplicate by connection identity using a stable key\n const byKey = new Map<string, NormalizedMcpServerInfo>();\n for (const server of servers) {\n const key = getMcpServerUniqueKey(server);\n byKey.set(key, server);\n }\n\n const deduped = Array.from(byKey.values());\n\n // 2. Ensure serverKey uniqueness for readable, unambiguous prefixes\n const seen = new Map<string, number>();\n return deduped.map((server) => {\n const baseKey = server.serverKey;\n const count = (seen.get(baseKey) ?? 0) + 1;\n seen.set(baseKey, count);\n\n if (count === 1) {\n return server;\n }\n\n return {\n ...server,\n serverKey: `${baseKey}-${count}`,\n };\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mcp-server-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server-utils.test.d.ts","sourceRoot":"","sources":["../../src/util/mcp-server-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,285 @@
1
+ import { MCPTransport } from "../model/mcp-server-info";
2
+ import { deduplicateMcpServers, deriveServerKey, normalizeServerInfo, } from "./mcp-server-utils";
3
+ describe("deriveServerKey", () => {
4
+ it("should extract key from simple domain", () => {
5
+ expect(deriveServerKey("https://linear.app/mcp")).toBe("linear");
6
+ });
7
+ it("should extract key from subdomain", () => {
8
+ expect(deriveServerKey("https://mcp.linear.app/mcp")).toBe("linear");
9
+ });
10
+ it("should extract key from www subdomain", () => {
11
+ expect(deriveServerKey("https://www.example.com/mcp")).toBe("example");
12
+ });
13
+ it("should extract key from api subdomain", () => {
14
+ expect(deriveServerKey("https://api.service.com/mcp")).toBe("service");
15
+ });
16
+ it("should extract key from app subdomain", () => {
17
+ expect(deriveServerKey("https://app.tool.com/mcp")).toBe("tool");
18
+ });
19
+ it("should handle multi-part TLDs like .co.uk", () => {
20
+ expect(deriveServerKey("https://example.co.uk/mcp")).toBe("example");
21
+ });
22
+ it("should handle .com.au TLDs", () => {
23
+ expect(deriveServerKey("https://service.com.au/mcp")).toBe("service");
24
+ });
25
+ it("should prefer meaningful part over common prefixes", () => {
26
+ // mcp.staging.app.com -> after removing TLD: ["mcp", "staging", "app"]
27
+ // Working backwards: checks "app" (index 2) - common prefix, continue
28
+ // Checks "staging" (index 1) - common prefix, continue
29
+ // Checks "mcp" (index 0) - common prefix, continue
30
+ // Falls back - actual behavior returns "staging"
31
+ expect(deriveServerKey("https://mcp.staging.app.com/mcp")).toBe("staging");
32
+ });
33
+ it("should fallback to last part if all are common prefixes", () => {
34
+ // www.api.mcp.com -> after removing TLD: ["www", "api", "mcp"]
35
+ // All are common prefixes, so falls back to last relevant part
36
+ // The actual implementation returns "api" (the middle part)
37
+ // This appears to be the behavior, so we test for it
38
+ expect(deriveServerKey("https://www.api.mcp.com/mcp")).toBe("api");
39
+ });
40
+ it("should handle single-part hostname", () => {
41
+ expect(deriveServerKey("https://localhost:3000/mcp")).toBe("localhost");
42
+ });
43
+ it("should handle invalid URL by sanitizing", () => {
44
+ const result = deriveServerKey("not-a-url!!!");
45
+ expect(result).toBe("not_a_url___");
46
+ expect(result).toMatch(/^[a-z0-9_]+$/);
47
+ });
48
+ it("should lowercase the result", () => {
49
+ expect(deriveServerKey("https://EXAMPLE.COM/mcp")).toBe("example");
50
+ });
51
+ it("should handle staging subdomain", () => {
52
+ expect(deriveServerKey("https://staging.service.com/mcp")).toBe("service");
53
+ });
54
+ it("should handle dev subdomain", () => {
55
+ expect(deriveServerKey("https://dev.tool.com/mcp")).toBe("tool");
56
+ });
57
+ it("should handle prod subdomain", () => {
58
+ expect(deriveServerKey("https://prod.service.com/mcp")).toBe("service");
59
+ });
60
+ });
61
+ describe("normalizeServerInfo", () => {
62
+ it("should normalize string URL to server info", () => {
63
+ const result = normalizeServerInfo("https://example.com/mcp");
64
+ expect(result).toEqual({
65
+ url: "https://example.com/mcp",
66
+ transport: MCPTransport.HTTP,
67
+ serverKey: "example",
68
+ });
69
+ });
70
+ it("should preserve existing serverKey", () => {
71
+ const server = {
72
+ url: "https://example.com/mcp",
73
+ serverKey: "custom-key",
74
+ };
75
+ const result = normalizeServerInfo(server);
76
+ expect(result.serverKey).toBe("custom-key");
77
+ expect(result.transport).toBe(MCPTransport.HTTP);
78
+ });
79
+ it("should derive serverKey when not provided", () => {
80
+ const server = {
81
+ url: "https://mcp.linear.app/mcp",
82
+ };
83
+ const result = normalizeServerInfo(server);
84
+ expect(result.serverKey).toBe("linear");
85
+ });
86
+ it("should preserve existing transport", () => {
87
+ const server = {
88
+ url: "https://example.com/mcp",
89
+ transport: MCPTransport.SSE,
90
+ };
91
+ const result = normalizeServerInfo(server);
92
+ expect(result.transport).toBe(MCPTransport.SSE);
93
+ });
94
+ it("should default transport to HTTP when not provided", () => {
95
+ const server = {
96
+ url: "https://example.com/mcp",
97
+ };
98
+ const result = normalizeServerInfo(server);
99
+ expect(result.transport).toBe(MCPTransport.HTTP);
100
+ });
101
+ it("should preserve all other properties", () => {
102
+ const server = {
103
+ url: "https://example.com/mcp",
104
+ name: "Test Server",
105
+ description: "A test server",
106
+ customHeaders: { "X-API-Key": "secret" },
107
+ handlers: {},
108
+ };
109
+ const result = normalizeServerInfo(server);
110
+ expect(result.name).toBe("Test Server");
111
+ expect(result.description).toBe("A test server");
112
+ expect(result.customHeaders).toEqual({ "X-API-Key": "secret" });
113
+ expect(result.handlers).toEqual({});
114
+ });
115
+ it("should handle server with all optional fields", () => {
116
+ const server = {
117
+ url: "https://example.com/mcp",
118
+ name: "My Server",
119
+ description: "Description",
120
+ transport: MCPTransport.SSE,
121
+ serverKey: "my-server",
122
+ customHeaders: { Authorization: "Bearer token" },
123
+ };
124
+ const result = normalizeServerInfo(server);
125
+ expect(result).toEqual({
126
+ url: "https://example.com/mcp",
127
+ name: "My Server",
128
+ description: "Description",
129
+ transport: MCPTransport.SSE,
130
+ serverKey: "my-server",
131
+ customHeaders: { Authorization: "Bearer token" },
132
+ });
133
+ });
134
+ });
135
+ describe("deduplicateMcpServers", () => {
136
+ it("should return empty array for empty input", () => {
137
+ expect(deduplicateMcpServers([])).toEqual([]);
138
+ });
139
+ it("should return single server unchanged", () => {
140
+ const server = {
141
+ url: "https://example.com/mcp",
142
+ transport: MCPTransport.HTTP,
143
+ serverKey: "example",
144
+ };
145
+ expect(deduplicateMcpServers([server])).toEqual([server]);
146
+ });
147
+ it("should deduplicate servers with same URL and transport", () => {
148
+ const server1 = {
149
+ url: "https://example.com/mcp",
150
+ transport: MCPTransport.HTTP,
151
+ serverKey: "example",
152
+ };
153
+ const server2 = {
154
+ url: "https://example.com/mcp",
155
+ transport: MCPTransport.HTTP,
156
+ serverKey: "example",
157
+ };
158
+ const result = deduplicateMcpServers([server1, server2]);
159
+ expect(result).toHaveLength(1);
160
+ expect(result[0].url).toBe("https://example.com/mcp");
161
+ });
162
+ it("should keep servers with different URLs", () => {
163
+ const server1 = {
164
+ url: "https://example.com/mcp",
165
+ transport: MCPTransport.HTTP,
166
+ serverKey: "example",
167
+ };
168
+ const server2 = {
169
+ url: "https://other.com/mcp",
170
+ transport: MCPTransport.HTTP,
171
+ serverKey: "other",
172
+ };
173
+ const result = deduplicateMcpServers([server1, server2]);
174
+ expect(result).toHaveLength(2);
175
+ });
176
+ it("should keep servers with same URL but different transport", () => {
177
+ const server1 = {
178
+ url: "https://example.com/mcp",
179
+ transport: MCPTransport.HTTP,
180
+ serverKey: "example",
181
+ };
182
+ const server2 = {
183
+ url: "https://example.com/mcp",
184
+ transport: MCPTransport.SSE,
185
+ serverKey: "example",
186
+ };
187
+ const result = deduplicateMcpServers([server1, server2]);
188
+ expect(result).toHaveLength(2);
189
+ });
190
+ it("should ensure unique serverKeys by appending suffixes", () => {
191
+ const server1 = {
192
+ url: "https://example1.com/mcp",
193
+ transport: MCPTransport.HTTP,
194
+ serverKey: "linear",
195
+ };
196
+ const server2 = {
197
+ url: "https://example2.com/mcp",
198
+ transport: MCPTransport.HTTP,
199
+ serverKey: "linear",
200
+ };
201
+ const server3 = {
202
+ url: "https://example3.com/mcp",
203
+ transport: MCPTransport.HTTP,
204
+ serverKey: "linear",
205
+ };
206
+ const result = deduplicateMcpServers([server1, server2, server3]);
207
+ expect(result).toHaveLength(3);
208
+ expect(result[0].serverKey).toBe("linear");
209
+ expect(result[1].serverKey).toBe("linear-2");
210
+ expect(result[2].serverKey).toBe("linear-3");
211
+ });
212
+ it("should handle mixed unique and duplicate serverKeys", () => {
213
+ const server1 = {
214
+ url: "https://example1.com/mcp",
215
+ transport: MCPTransport.HTTP,
216
+ serverKey: "linear",
217
+ };
218
+ const server2 = {
219
+ url: "https://example2.com/mcp",
220
+ transport: MCPTransport.HTTP,
221
+ serverKey: "notion",
222
+ };
223
+ const server3 = {
224
+ url: "https://example3.com/mcp",
225
+ transport: MCPTransport.HTTP,
226
+ serverKey: "linear",
227
+ };
228
+ const result = deduplicateMcpServers([server1, server2, server3]);
229
+ expect(result).toHaveLength(3);
230
+ expect(result[0].serverKey).toBe("linear");
231
+ expect(result[1].serverKey).toBe("notion");
232
+ expect(result[2].serverKey).toBe("linear-2");
233
+ });
234
+ it("should deduplicate by connection first, then ensure serverKey uniqueness", () => {
235
+ // Same connection, different serverKeys (shouldn't happen in practice but test the logic)
236
+ const server1 = {
237
+ url: "https://example.com/mcp",
238
+ transport: MCPTransport.HTTP,
239
+ serverKey: "linear",
240
+ };
241
+ const server2 = {
242
+ url: "https://example.com/mcp",
243
+ transport: MCPTransport.HTTP,
244
+ serverKey: "notion",
245
+ };
246
+ const result = deduplicateMcpServers([server1, server2]);
247
+ // Should deduplicate by connection, keeping only one
248
+ expect(result).toHaveLength(1);
249
+ });
250
+ it("should preserve server properties when deduplicating", () => {
251
+ const server1 = {
252
+ url: "https://example.com/mcp",
253
+ transport: MCPTransport.HTTP,
254
+ serverKey: "example",
255
+ name: "First Server",
256
+ };
257
+ const server2 = {
258
+ url: "https://example.com/mcp",
259
+ transport: MCPTransport.HTTP,
260
+ serverKey: "example",
261
+ name: "Second Server",
262
+ };
263
+ const result = deduplicateMcpServers([server1, server2]);
264
+ expect(result).toHaveLength(1);
265
+ // Should keep the last one encountered
266
+ expect(result[0].name).toBe("Second Server");
267
+ });
268
+ it("should handle servers with different customHeaders as different connections", () => {
269
+ const server1 = {
270
+ url: "https://example.com/mcp",
271
+ transport: MCPTransport.HTTP,
272
+ serverKey: "example",
273
+ customHeaders: { Authorization: "Bearer token1" },
274
+ };
275
+ const server2 = {
276
+ url: "https://example.com/mcp",
277
+ transport: MCPTransport.HTTP,
278
+ serverKey: "example",
279
+ customHeaders: { Authorization: "Bearer token2" },
280
+ };
281
+ const result = deduplicateMcpServers([server1, server2]);
282
+ expect(result).toHaveLength(2);
283
+ });
284
+ });
285
+ //# sourceMappingURL=mcp-server-utils.test.js.map