@bevel-software/platform-core-backend 0.26.0 → 0.27.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 (613) hide show
  1. package/agent-guide/access-control.md +17 -0
  2. package/agent-guide/conventions.md +8 -0
  3. package/agent-guide/html-views.md +83 -0
  4. package/agent-guide/new-knowledge-base.md +24 -0
  5. package/agent-guide/tool-manuals.md +10 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.d.ts.map +1 -1
  8. package/dist/assets.js +26 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/core/catalog-revision.d.ts +19 -1
  11. package/dist/core/catalog-revision.d.ts.map +1 -1
  12. package/dist/core/catalog-revision.js +14 -3
  13. package/dist/core/catalog-revision.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts +5 -0
  15. package/dist/core/create-core-server.d.ts.map +1 -1
  16. package/dist/core/create-core-server.js +54 -3
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +26 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +100 -8
  21. package/dist/core/create-core-services.js.map +1 -1
  22. package/dist/core/lifecycle.d.ts +16 -1
  23. package/dist/core/lifecycle.d.ts.map +1 -1
  24. package/dist/core/lifecycle.js +18 -1
  25. package/dist/core/lifecycle.js.map +1 -1
  26. package/dist/core-config.d.ts +21 -0
  27. package/dist/core-config.d.ts.map +1 -1
  28. package/dist/core-config.js +22 -1
  29. package/dist/core-config.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/access/access-control.interface.d.ts +8 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +1 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +11 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/directory-sync-bot.js +1 -1
  41. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  42. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  43. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  44. package/dist/modules/agent-guide/agent-guide.js +8 -0
  45. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  46. package/dist/modules/agent-guide/index.d.ts +1 -1
  47. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  48. package/dist/modules/agent-guide/index.js +1 -1
  49. package/dist/modules/agent-guide/index.js.map +1 -1
  50. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  51. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/compose.js +19 -2
  53. package/dist/modules/agent-instructions/compose.js.map +1 -1
  54. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  55. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  56. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  57. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  58. package/dist/modules/audit/agent-audit.service.js +43 -2
  59. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  60. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  61. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  62. package/dist/modules/audit/agent-connection.routes.js +38 -0
  63. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  64. package/dist/modules/audit/audit.contract.d.ts +24 -0
  65. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  66. package/dist/modules/audit/audit.contract.js.map +1 -1
  67. package/dist/modules/audit/index.d.ts +1 -0
  68. package/dist/modules/audit/index.d.ts.map +1 -1
  69. package/dist/modules/audit/index.js +1 -0
  70. package/dist/modules/audit/index.js.map +1 -1
  71. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  72. package/dist/modules/auth/account.routes.js +10 -3
  73. package/dist/modules/auth/account.routes.js.map +1 -1
  74. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  75. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  76. package/dist/modules/auth/auth.middleware.js +1 -1
  77. package/dist/modules/auth/auth.middleware.js.map +1 -1
  78. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  79. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  80. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  81. package/dist/modules/database/core-schema.d.ts +96 -0
  82. package/dist/modules/database/core-schema.d.ts.map +1 -1
  83. package/dist/modules/database/core-schema.js +32 -3
  84. package/dist/modules/database/core-schema.js.map +1 -1
  85. package/dist/modules/embed/account-link.service.d.ts +42 -0
  86. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  87. package/dist/modules/embed/account-link.service.js +50 -0
  88. package/dist/modules/embed/account-link.service.js.map +1 -0
  89. package/dist/modules/embed/embed-link.d.ts +58 -0
  90. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  91. package/dist/modules/embed/embed-link.js +160 -0
  92. package/dist/modules/embed/embed-link.js.map +1 -0
  93. package/dist/modules/embed/embed.errors.d.ts +30 -0
  94. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  95. package/dist/modules/embed/embed.errors.js +42 -0
  96. package/dist/modules/embed/embed.errors.js.map +1 -0
  97. package/dist/modules/embed/embed.interface.d.ts +204 -0
  98. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  99. package/dist/modules/embed/embed.interface.js +2 -0
  100. package/dist/modules/embed/embed.interface.js.map +1 -0
  101. package/dist/modules/embed/embed.routes.d.ts +59 -0
  102. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  103. package/dist/modules/embed/embed.routes.js +343 -0
  104. package/dist/modules/embed/embed.routes.js.map +1 -0
  105. package/dist/modules/embed/embed.service.d.ts +169 -0
  106. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  107. package/dist/modules/embed/embed.service.js +627 -0
  108. package/dist/modules/embed/embed.service.js.map +1 -0
  109. package/dist/modules/embed/embed.tools.d.ts +51 -0
  110. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  111. package/dist/modules/embed/embed.tools.js +170 -0
  112. package/dist/modules/embed/embed.tools.js.map +1 -0
  113. package/dist/modules/embed/mcp-app.d.ts +64 -0
  114. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  115. package/dist/modules/embed/mcp-app.js +147 -0
  116. package/dist/modules/embed/mcp-app.js.map +1 -0
  117. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  118. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  119. package/dist/modules/embed/mcp-app.routes.js +36 -0
  120. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  121. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  122. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  123. package/dist/modules/github-app/github-app.connection.js +34 -3
  124. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  125. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  126. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  127. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  128. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  129. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  130. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  131. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  132. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  133. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  134. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  135. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  136. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  137. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  138. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  139. package/dist/modules/mcp/mcp.service.js +64 -8
  140. package/dist/modules/mcp/mcp.service.js.map +1 -1
  141. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  142. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  143. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  144. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  145. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  146. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  147. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  148. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  149. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  150. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  151. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  152. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  153. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  154. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  155. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  156. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  157. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  158. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  159. package/dist/modules/onboarding/starter-packs.js +156 -0
  160. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  161. package/dist/modules/plugins/index.d.ts +1 -1
  162. package/dist/modules/plugins/index.d.ts.map +1 -1
  163. package/dist/modules/plugins/index.js +1 -1
  164. package/dist/modules/plugins/index.js.map +1 -1
  165. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  166. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  167. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  168. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  169. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  170. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  171. package/dist/modules/plugins/plugins.routes.js +31 -0
  172. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  173. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  174. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  175. package/dist/modules/settings/deployment-settings.service.js +134 -1
  176. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  177. package/dist/modules/settings/repository-source.d.ts +1 -0
  178. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  179. package/dist/modules/settings/repository-source.js +3 -3
  180. package/dist/modules/settings/repository-source.js.map +1 -1
  181. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  182. package/dist/modules/skills/skills.tools.js +50 -32
  183. package/dist/modules/skills/skills.tools.js.map +1 -1
  184. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  185. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  186. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  187. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  188. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  189. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  190. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  191. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  192. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  193. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  194. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  195. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  196. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  197. package/dist/modules/tool-helpers/index.d.ts +28 -1
  198. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  199. package/dist/modules/tool-helpers/index.js +28 -1
  200. package/dist/modules/tool-helpers/index.js.map +1 -1
  201. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  202. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  203. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  204. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  205. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  206. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  207. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  208. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  209. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  210. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  211. package/dist/modules/tool-helpers/tool-def.js +70 -6
  212. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  213. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  214. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  215. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  216. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  217. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  218. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  219. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  220. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  221. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  222. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  223. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  224. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  225. package/dist/modules/tool-registry/description-length.js +27 -5
  226. package/dist/modules/tool-registry/description-length.js.map +1 -1
  227. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  228. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  229. package/dist/modules/tool-registry/guide-first.js +9 -1
  230. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  231. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  232. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  233. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  234. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  235. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  236. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  237. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  238. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  239. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  240. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  241. package/dist/modules/workflow/file-lock.service.js +62 -5
  242. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  243. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  244. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  245. package/dist/modules/workflow/git/git.service.js +172 -36
  246. package/dist/modules/workflow/git/git.service.js.map +1 -1
  247. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  248. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  249. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  250. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  251. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  252. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  253. package/dist/modules/workflow/pending-commits.service.js +25 -1
  254. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  255. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  256. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  257. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  258. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  259. package/dist/modules/workflow/recovery-bot.js +1 -1
  260. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  261. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  263. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  264. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  265. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  266. package/dist/modules/workflow/sanitize-error.js +45 -0
  267. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  268. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  269. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  270. package/dist/modules/workflow/workflow.service.js +805 -106
  271. package/dist/modules/workflow/workflow.service.js.map +1 -1
  272. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  273. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  274. package/dist/modules/workspace/agent-download.builder.js +238 -0
  275. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  276. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  277. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  278. package/dist/modules/workspace/agent-download.routes.js +156 -0
  279. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  280. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  281. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  282. package/dist/modules/workspace/agent-download.store.js +423 -0
  283. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  284. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  285. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  286. package/dist/modules/workspace/agent-upload.store.js +16 -6
  287. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  288. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  289. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  290. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  291. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  292. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  293. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  294. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  295. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  296. package/dist/modules/workspace/first-run.d.ts +111 -0
  297. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  298. package/dist/modules/workspace/first-run.js +181 -0
  299. package/dist/modules/workspace/first-run.js.map +1 -0
  300. package/dist/modules/workspace/move-links.d.ts +92 -0
  301. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  302. package/dist/modules/workspace/move-links.js +173 -0
  303. package/dist/modules/workspace/move-links.js.map +1 -0
  304. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  305. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  306. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  307. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  308. package/dist/modules/workspace/workspace.routes.js +63 -22
  309. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  310. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  311. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  312. package/dist/modules/workspace/workspace.service.js +197 -24
  313. package/dist/modules/workspace/workspace.service.js.map +1 -1
  314. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  315. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  316. package/dist/modules/workspace/workspace.tools.js +443 -65
  317. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  318. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  319. package/dist/modules/write-access/write-access.js +9 -0
  320. package/dist/modules/write-access/write-access.js.map +1 -1
  321. package/dist/shared/domain-errors.d.ts +73 -1
  322. package/dist/shared/domain-errors.d.ts.map +1 -1
  323. package/dist/shared/domain-errors.js +86 -12
  324. package/dist/shared/domain-errors.js.map +1 -1
  325. package/dist/shared/git.contract.d.ts +10 -1
  326. package/dist/shared/git.contract.d.ts.map +1 -1
  327. package/dist/shared/git.contract.js.map +1 -1
  328. package/dist/shared/utf8-text.d.ts +15 -0
  329. package/dist/shared/utf8-text.d.ts.map +1 -0
  330. package/dist/shared/utf8-text.js +20 -0
  331. package/dist/shared/utf8-text.js.map +1 -0
  332. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  333. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  334. package/dist/tenancy/static-tenant-source.js +9 -2
  335. package/dist/tenancy/static-tenant-source.js.map +1 -1
  336. package/mcp-app/page.html +349 -0
  337. package/migrations/0017_atlassian_account_links.sql +37 -0
  338. package/migrations/meta/0017_snapshot.json +2396 -0
  339. package/migrations/meta/_journal.json +8 -1
  340. package/package.json +5 -3
  341. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  342. package/src/assets.ts +28 -2
  343. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  344. package/src/core/__tests__/gated-boot.test.ts +1 -0
  345. package/src/core/__tests__/lifecycle.test.ts +14 -0
  346. package/src/core/catalog-revision.ts +15 -2
  347. package/src/core/create-core-server.ts +67 -3
  348. package/src/core/create-core-services.ts +140 -6
  349. package/src/core/lifecycle.ts +28 -2
  350. package/src/core-config.ts +24 -1
  351. package/src/index.ts +2 -2
  352. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  353. package/src/modules/access/access-control.interface.ts +13 -0
  354. package/src/modules/access/access-control.service.ts +16 -0
  355. package/src/modules/access/directory-sync-bot.ts +1 -1
  356. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  357. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  358. package/src/modules/agent-guide/agent-guide.ts +9 -0
  359. package/src/modules/agent-guide/index.ts +1 -0
  360. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  361. package/src/modules/agent-instructions/compose.ts +17 -2
  362. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  363. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  364. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  365. package/src/modules/audit/agent-audit.service.ts +58 -3
  366. package/src/modules/audit/agent-connection.routes.ts +56 -0
  367. package/src/modules/audit/audit.contract.ts +26 -0
  368. package/src/modules/audit/index.ts +1 -0
  369. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  370. package/src/modules/auth/account.routes.ts +10 -3
  371. package/src/modules/auth/auth.middleware.ts +1 -1
  372. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  373. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  374. package/src/modules/database/core-schema.ts +33 -3
  375. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  376. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  377. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  378. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  379. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  380. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  381. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  382. package/src/modules/embed/account-link.service.ts +76 -0
  383. package/src/modules/embed/embed-link.ts +180 -0
  384. package/src/modules/embed/embed.errors.ts +43 -0
  385. package/src/modules/embed/embed.interface.ts +203 -0
  386. package/src/modules/embed/embed.routes.ts +363 -0
  387. package/src/modules/embed/embed.service.ts +705 -0
  388. package/src/modules/embed/embed.tools.ts +206 -0
  389. package/src/modules/embed/mcp-app.routes.ts +41 -0
  390. package/src/modules/embed/mcp-app.ts +173 -0
  391. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  392. package/src/modules/github-app/github-app.connection.ts +38 -4
  393. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  394. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  395. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  396. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  397. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  398. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  399. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  400. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  401. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  402. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  403. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  404. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  405. package/src/modules/mcp/mcp.service.ts +81 -12
  406. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  407. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  408. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  409. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  410. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  411. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  412. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  413. package/src/modules/onboarding/starter-packs.ts +182 -0
  414. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  415. package/src/modules/plugins/index.ts +1 -0
  416. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  417. package/src/modules/plugins/plugins.routes.ts +29 -0
  418. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  419. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  420. package/src/modules/settings/deployment-settings.service.ts +131 -1
  421. package/src/modules/settings/repository-source.ts +4 -4
  422. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  423. package/src/modules/skills/skills.tools.ts +55 -35
  424. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  425. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  426. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  427. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  428. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  429. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  430. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  431. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  432. package/src/modules/tool-helpers/index.ts +34 -1
  433. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  434. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  435. package/src/modules/tool-helpers/tool-def.ts +132 -7
  436. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  437. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  438. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  439. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  440. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  441. package/src/modules/tool-registry/description-length.ts +34 -7
  442. package/src/modules/tool-registry/guide-first.ts +8 -1
  443. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  444. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  445. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  446. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  447. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  448. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  449. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  450. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  451. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  452. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  453. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  454. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  455. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  457. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  458. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  459. package/src/modules/workflow/file-lock.service.ts +67 -5
  460. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  461. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  462. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  463. package/src/modules/workflow/git/git.service.ts +176 -38
  464. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  465. package/src/modules/workflow/pending-commits.service.ts +29 -1
  466. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  467. package/src/modules/workflow/recovery-bot.ts +1 -1
  468. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  469. package/src/modules/workflow/sanitize-error.ts +50 -0
  470. package/src/modules/workflow/workflow.service.ts +879 -112
  471. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  472. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  473. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  474. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  475. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  476. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  477. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  478. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  479. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  480. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  481. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  482. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  483. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  484. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  485. package/src/modules/workspace/agent-download.builder.ts +308 -0
  486. package/src/modules/workspace/agent-download.routes.ts +183 -0
  487. package/src/modules/workspace/agent-download.store.ts +533 -0
  488. package/src/modules/workspace/agent-upload.store.ts +23 -7
  489. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  490. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  491. package/src/modules/workspace/first-run.ts +222 -0
  492. package/src/modules/workspace/move-links.ts +237 -0
  493. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  494. package/src/modules/workspace/workspace.routes.ts +64 -22
  495. package/src/modules/workspace/workspace.service.ts +202 -25
  496. package/src/modules/workspace/workspace.tools.ts +492 -63
  497. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  498. package/src/modules/write-access/write-access.ts +9 -0
  499. package/src/shared/domain-errors.ts +108 -11
  500. package/src/shared/git.contract.ts +8 -1
  501. package/src/shared/utf8-text.ts +21 -0
  502. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  503. package/src/tenancy/static-tenant-source.ts +10 -2
  504. package/starter-packs/README.md +103 -0
  505. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  506. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  507. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  508. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  509. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  510. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  511. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  512. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  513. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  514. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  515. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  516. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  517. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  518. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  519. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  520. package/starter-packs/engineering/pack.yaml +11 -0
  521. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  522. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  523. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  524. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  525. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  526. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  527. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  528. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  529. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  530. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  531. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  532. package/starter-packs/general/pack.yaml +10 -0
  533. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  534. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  535. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  536. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  537. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  538. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  539. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  540. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  541. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  542. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  543. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  544. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  545. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  546. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  547. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  548. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  549. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  550. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  551. package/starter-packs/operations/pack.yaml +12 -0
  552. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  553. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  554. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  555. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  556. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  557. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  558. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  559. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  560. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  561. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  562. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  563. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  564. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  565. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  566. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  567. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  568. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  569. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  570. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  571. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  572. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  573. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  574. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  575. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  576. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  577. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  578. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  579. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  580. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  581. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  582. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  583. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  584. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  585. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  586. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  587. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  588. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  589. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  590. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  591. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  592. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  593. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  594. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  595. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  596. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  597. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  598. package/starter-packs/sales/pack.yaml +13 -0
  599. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  600. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  601. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  602. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  603. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  604. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  605. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  606. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  607. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  608. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  609. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  610. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  611. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  612. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  613. package/starter-packs/support/pack.yaml +12 -0
@@ -0,0 +1,499 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import express, { type RequestHandler } from 'express';
6
+ import { afterEach, describe, expect, it } from 'vitest';
7
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
9
+ import { createMcpRoutes } from '../mcp.routes.js';
10
+ import { McpService } from '../mcp.service.js';
11
+ import { SpillStore } from '../../workspace/spill-store.js';
12
+ import { createManualRoutes } from '../../tool-registry/manual.routes.js';
13
+ import { ToolRegistry } from '../../tool-registry/tool-registry.js';
14
+ import { toolDef, withBranchInput } from '../../tool-helpers/tool-def.js';
15
+ import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
16
+ import type { ToolContext } from '../../tool-helpers/tool.contract.js';
17
+ import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
18
+ import { assertBranchProvided, BRANCH_REQUIRED_MESSAGE } from '../../../shared/domain-errors.js';
19
+ import { PLATFORM_HEADER } from '../../agent-instructions/index.js';
20
+
21
+ /**
22
+ * Every Scenario of "Tool calls checked against the interface", over the real
23
+ * MCP transport: the call example at the top of every description, the refusal
24
+ * a call that does not match its tool gets on the direct path AND inside
25
+ * `call_tool_chain`, and a matching call reaching its endpoint untouched.
26
+ *
27
+ * Two kinds of tool are in the catalog, because the whole point is that they
28
+ * are called differently, and because the check for each lives somewhere else:
29
+ * - `ask` and `read_file`, platform tools — `toolDef` wraps their arguments in
30
+ * `body`, they are hosted as ROUTES here, and their own route handler
31
+ * (the real `toolHandler`) checks every call, wherever it came from;
32
+ * - `search`, a tool a deployment adds through a manual of its own, with FLAT
33
+ * arguments as every tool that calls another service has, and NO route
34
+ * here — so the tool client checks it before the call leaves.
35
+ *
36
+ * Which is why every refusal below is also asked of `POST /api/agent/tools/<name>`
37
+ * directly: a script with a connection key must get the same answer an agent does.
38
+ */
39
+
40
+ const KEY = 'bevel_key_user_a';
41
+ const bearerOf = (req: express.Request) => (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
42
+
43
+ const fakeAuth: RequestHandler = (req, res, next) => {
44
+ if (bearerOf(req) !== KEY) {
45
+ res.status(401).json({ error: 'unauthorized' });
46
+ return;
47
+ }
48
+ req.userId = 'user-A';
49
+ req.externalApiKeyId = 'tok-A';
50
+ next();
51
+ };
52
+
53
+ const passthrough: RequestHandler = (_req, _res, next) => next();
54
+
55
+ interface Platform {
56
+ baseUrl: string;
57
+ /** Every call the deployment's own `search` endpoint received, verbatim. */
58
+ searchCalls: Array<{ body: unknown; query: unknown }>;
59
+ /** Every call that reached the `ask` HANDLER — empty when a refusal ran nothing. */
60
+ askCalls: Array<Record<string, unknown>>;
61
+ stop(): Promise<void>;
62
+ }
63
+
64
+ const platforms: Platform[] = [];
65
+ const cleanups: Array<() => Promise<void>> = [];
66
+
67
+ async function startPlatform(): Promise<Platform> {
68
+ const searchCalls: Platform['searchCalls'] = [];
69
+ const askCalls: Platform['askCalls'] = [];
70
+ const registry = new ToolRegistry();
71
+ registry.registerExternalTool(
72
+ toolDef({
73
+ name: 'ask',
74
+ description: 'Ask a question.',
75
+ path: '/api/agent/tools/ask',
76
+ inputs: {
77
+ type: 'object',
78
+ properties: {
79
+ prompt: { type: 'string', description: 'What to ask.' },
80
+ sessionId: { type: 'string', description: 'A session to continue.' },
81
+ },
82
+ required: ['prompt'],
83
+ additionalProperties: false,
84
+ },
85
+ }),
86
+ );
87
+
88
+ // A knowledge-base tool, with the required `branch` every one of them takes:
89
+ // its own refusal for a missing branch must survive the argument check.
90
+ registry.registerExternalTool(
91
+ toolDef({
92
+ name: 'read_file',
93
+ description: 'Read a workspace file as text.',
94
+ path: '/api/agent/tools/read_file',
95
+ inputs: withBranchInput({
96
+ type: 'object',
97
+ properties: { path: { type: 'string', description: 'Path to read.' } },
98
+ required: ['path'],
99
+ additionalProperties: false,
100
+ }),
101
+ }),
102
+ );
103
+
104
+ const app = express();
105
+ app.use(express.json());
106
+ // The two platform tools are hosted through the REAL `toolHandler`, because
107
+ // the route's handler is where the check lives: a test that mounted a bare
108
+ // Express handler would prove nothing about the answer an agent gets.
109
+ const toolHandler = createToolHandlerFactory(
110
+ async (auth: ToolAuth, abortSignal: AbortSignal): Promise<ToolContext> =>
111
+ ({
112
+ user: { id: 'user-A', email: 'a@x.io', name: 'A' },
113
+ scope: auth.scope,
114
+ source: auth.source,
115
+ abortSignal,
116
+ }) as unknown as ToolContext,
117
+ );
118
+ const asTool: RequestHandler = (req, _res, next) => {
119
+ req.toolAuth = { source: 'external', userId: 'user-A', scope: 'write' };
120
+ next();
121
+ };
122
+ app.post(
123
+ '/api/agent/tools/ask',
124
+ asTool,
125
+ toolHandler(async (args) => {
126
+ askCalls.push(args);
127
+ return { text: `echo: ${args.prompt as string}` };
128
+ }),
129
+ );
130
+ // The real boundary guard too, so the refusal for a missing branch under test
131
+ // is the platform's own and keeps its own wording.
132
+ app.post(
133
+ '/api/agent/tools/read_file',
134
+ asTool,
135
+ toolHandler(async (args) => {
136
+ assertBranchProvided(args.branch);
137
+ return { path: args.path, content: 'contents' };
138
+ }),
139
+ );
140
+ // The deployment's own tool: flat arguments, like every connector tool.
141
+ app.get('/api/third-party/manual', (_req, res) =>
142
+ res.json({
143
+ utcp_version: '1.1.0',
144
+ manual_version: '1.0.0',
145
+ tools: [
146
+ {
147
+ name: 'search',
148
+ description: 'Search the other service.',
149
+ inputs: {
150
+ type: 'object',
151
+ properties: {
152
+ query: { type: 'string', description: 'The search text.' },
153
+ limit: { type: 'integer', description: 'How many results.' },
154
+ },
155
+ required: ['query'],
156
+ additionalProperties: false,
157
+ },
158
+ outputs: { type: 'object', properties: {} },
159
+ tags: [],
160
+ // No `body_field` named, so the default (`body`) matches no
161
+ // argument this tool declares and every argument rides the query
162
+ // string — the shape a connector tool really has.
163
+ tool_call_template: {
164
+ call_template_type: 'http',
165
+ http_method: 'POST',
166
+ url: `\${API_URL}/api/third-party/search`,
167
+ content_type: 'application/json',
168
+ },
169
+ },
170
+ ],
171
+ }),
172
+ );
173
+ app.post('/api/third-party/search', (req, res) => {
174
+ searchCalls.push({ body: req.body, query: req.query });
175
+ res.json({ hits: [] });
176
+ });
177
+ app.get('/api/agent/all-tools', (_req, res) => {
178
+ res.json({
179
+ manuals: [
180
+ {
181
+ name: 'KNOWLEDGE_BASE',
182
+ call_template_type: 'http',
183
+ http_method: 'GET',
184
+ url: '${API_URL}/api/agent/utcp',
185
+ content_type: 'application/json',
186
+ headers: { Authorization: 'Bearer ${CONNECTION_KEY}' },
187
+ },
188
+ {
189
+ name: 'THIRD_PARTY',
190
+ call_template_type: 'http',
191
+ http_method: 'GET',
192
+ url: '${API_URL}/api/third-party/manual',
193
+ content_type: 'application/json',
194
+ },
195
+ ],
196
+ });
197
+ });
198
+
199
+ const httpServer = await new Promise<HttpServer>((resolve) => {
200
+ const s = app.listen(0, '127.0.0.1', () => resolve(s));
201
+ });
202
+ const { port } = httpServer.address() as AddressInfo;
203
+ const baseUrl = `http://127.0.0.1:${port}`;
204
+
205
+ const service = new McpService({
206
+ loopbackBaseUrl: baseUrl,
207
+ manualName: 'KNOWLEDGE_BASE',
208
+ spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
209
+ publicFrontendUrl: 'http://localhost:5173',
210
+ });
211
+ const stub = {} as never;
212
+ app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
213
+ app.use('/api', createManualRoutes(registry, passthrough));
214
+
215
+ let stopped = false;
216
+ const platform: Platform = {
217
+ baseUrl,
218
+ searchCalls,
219
+ askCalls,
220
+ async stop() {
221
+ if (stopped) return;
222
+ stopped = true;
223
+ service.onSecretsChanged(null);
224
+ httpServer.closeAllConnections();
225
+ await new Promise<void>((r) => httpServer.close(() => r()));
226
+ },
227
+ };
228
+ platforms.push(platform);
229
+ return platform;
230
+ }
231
+
232
+ async function connect(baseUrl: string): Promise<Client> {
233
+ const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/api/mcp`), {
234
+ requestInit: { headers: { Authorization: `Bearer ${KEY}` } },
235
+ });
236
+ const client = new Client({ name: 'interface-e2e', version: '0.0.0' }, { capabilities: {} });
237
+ await client.connect(transport);
238
+ cleanups.push(async () => {
239
+ await client.close();
240
+ });
241
+ return client;
242
+ }
243
+
244
+ const toolText = (res: { content?: unknown }) => (res.content as Array<{ text: string }>)[0].text;
245
+ /** The REST route a script or a runner calls, with a connection key. */
246
+ const callRoute = (baseUrl: string, tool: string, body: unknown) =>
247
+ fetch(`${baseUrl}/api/agent/tools/${tool}`, {
248
+ method: 'POST',
249
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` },
250
+ body: JSON.stringify(body),
251
+ });
252
+
253
+
254
+ afterEach(async () => {
255
+ for (const c of cleanups.splice(0)) await c().catch(() => {});
256
+ for (const p of platforms.splice(0)) await p.stop().catch(() => {});
257
+ });
258
+
259
+ describe('every description opens with its call', () => {
260
+ it('shows a platform tool with its arguments under `body`, and a flat tool at the top level', async () => {
261
+ const { baseUrl } = await startPlatform();
262
+ const client = await connect(baseUrl);
263
+ const { tools } = await client.listTools();
264
+ const byName = Object.fromEntries(tools.map((t) => [t.name, t]));
265
+
266
+ expect(byName.ask.description?.split('\n')[0]).toBe('Call: KNOWLEDGE_BASE.ask({ body: { prompt: "..." } })');
267
+ expect(byName.THIRD_PARTY_search.description?.split('\n')[0]).toBe('Call: THIRD_PARTY.search({ query: "..." })');
268
+ // Every tool, the meta-tools included — an agent learns one rule, not a
269
+ // rule with exceptions.
270
+ for (const tool of tools) {
271
+ expect(tool.description?.startsWith('Call: '), tool.name).toBe(true);
272
+ expect(tool.description?.split('\n')[0].length, tool.name).toBeLessThan(200);
273
+ }
274
+ });
275
+
276
+ it('keeps the call line first even for a tool that also carries the purpose prefix', async () => {
277
+ const { baseUrl } = await startPlatform();
278
+ const client = await connect(baseUrl);
279
+ const { tools } = await client.listTools();
280
+ // This `McpService` is built without a `toolPrefix`, so no tool here —
281
+ // `read_file`, one of the prefixed four, included — carries the purpose
282
+ // prefix; that is exercised in the composer's own tests and in
283
+ // mcp.service.test.ts. What this pins is that nothing in the listing path
284
+ // (the guide-first opening included) puts text ahead of the line.
285
+ for (const tool of tools) expect(tool.description?.indexOf('Call: '), tool.name).toBe(0);
286
+ });
287
+
288
+ it('tells the agent to follow each tool\'s call line, and states no single shape', async () => {
289
+ const { baseUrl } = await startPlatform();
290
+ const client = await connect(baseUrl);
291
+ const chain = (await client.listTools()).tools.find((t) => t.name === 'call_tool_chain')!;
292
+ expect(chain.description).toContain('`Call:` line');
293
+ expect(chain.description).toContain('NO single calling shape');
294
+ // No phrasing of the one-shape rule, however it is spelled or interpolated.
295
+ expect(chain.description).not.toContain('<tool>({ body: { ...args } })');
296
+ // And the server instructions, which the handshake carries.
297
+ expect(PLATFORM_HEADER).toContain('`Call:` line');
298
+ expect(client.getInstructions()).toContain('`Call:` line');
299
+ });
300
+ });
301
+
302
+ describe('a call whose arguments do not match the tool', () => {
303
+ it('is refused with the interface and the example, and nothing is sent', async () => {
304
+ const platform = await startPlatform();
305
+ const client = await connect(platform.baseUrl);
306
+ const res = await client.callTool({ name: 'THIRD_PARTY_search', arguments: { body: { query: 'x' } } });
307
+ expect(res.isError).toBe(true);
308
+ const text = toolText(res);
309
+ expect(text).toContain('The arguments do not match the "search" tool.');
310
+ expect(text).toContain('This tool takes its arguments at the top level, not under "body".');
311
+ expect(text).toContain('"query" is required, and was not given.');
312
+ expect(text).toContain('query (string, required) — The search text.');
313
+ expect(text).toContain('limit (integer, optional)');
314
+ expect(text).toContain('Call: THIRD_PARTY.search({ query: "..." })');
315
+ expect(text).toContain('"kind":"arguments-do-not-match"');
316
+ // The other service was never reached.
317
+ expect(platform.searchCalls).toEqual([]);
318
+ });
319
+
320
+ it('names a required argument that is missing, and gives that tool\'s interface', async () => {
321
+ const platform = await startPlatform();
322
+ const client = await connect(platform.baseUrl);
323
+ const res = await client.callTool({ name: 'ask', arguments: { body: { sessionId: 's' } } });
324
+ expect(res.isError).toBe(true);
325
+ const text = toolText(res);
326
+ expect(text).toContain('The arguments do not match the "ask" tool.');
327
+ // Named as the tool takes it: the handler receives `prompt`, and so does a
328
+ // script on the REST route. The envelope shows up where it belongs — in the
329
+ // call example, which is the line an agent types.
330
+ expect(text).toContain('"prompt" is required, and was not given.');
331
+ expect(text).toContain('prompt (string, required) — What to ask.');
332
+ expect(text).toContain('Call: KNOWLEDGE_BASE.ask({ body: { prompt: "..." } })');
333
+ expect(text).toContain('"kind":"arguments-do-not-match"');
334
+ // Nothing ran: the handler was never reached.
335
+ expect(platform.askCalls).toEqual([]);
336
+ });
337
+
338
+ it('names an argument of the wrong type', async () => {
339
+ const { baseUrl } = await startPlatform();
340
+ const client = await connect(baseUrl);
341
+ const res = await client.callTool({ name: 'THIRD_PARTY_search', arguments: { query: 'x', limit: 'ten' } });
342
+ expect(res.isError).toBe(true);
343
+ expect(toolText(res)).toContain('"limit" must be integer, but string was given.');
344
+ });
345
+
346
+ it('names an argument the tool does not have', async () => {
347
+ const { baseUrl } = await startPlatform();
348
+ const client = await connect(baseUrl);
349
+ const res = await client.callTool({ name: 'THIRD_PARTY_search', arguments: { query: 'x', nope: 1 } });
350
+ expect(res.isError).toBe(true);
351
+ expect(toolText(res)).toContain('"nope" is not an argument of this tool.');
352
+ });
353
+
354
+ it('leaves a missing branch to the refusal that names it, word for word', async () => {
355
+ const platform = await startPlatform();
356
+ const client = await connect(platform.baseUrl);
357
+ const res = await client.callTool({ name: 'read_file', arguments: { body: { path: 'a.md' } } });
358
+ expect(res.isError).toBe(true);
359
+ const text = toolText(res);
360
+ expect(text).toContain(BRANCH_REQUIRED_MESSAGE);
361
+ expect(text).toContain('"kind":"branch-required"');
362
+ expect(text).not.toContain('arguments do not match');
363
+ });
364
+
365
+ it('fails the chain at a route-hosted tool too, with the route\'s own message', async () => {
366
+ const platform = await startPlatform();
367
+ const client = await connect(platform.baseUrl);
368
+ const res = await client.callTool({
369
+ name: 'call_tool_chain',
370
+ arguments: { code: 'return KNOWLEDGE_BASE.ask({ body: { sessionId: \'s\' } });' },
371
+ });
372
+ const text = toolText(res);
373
+ expect(text).toContain('The arguments do not match the \\"ask\\" tool.');
374
+ expect(text).toContain('\\"prompt\\" is required, and was not given.');
375
+ expect(text).toContain('Call: KNOWLEDGE_BASE.ask({ body: { prompt: \\"...\\" } })');
376
+ expect(platform.askCalls).toEqual([]);
377
+ });
378
+
379
+ it('fails the chain at that call, with the same message', async () => {
380
+ const platform = await startPlatform();
381
+ const client = await connect(platform.baseUrl);
382
+ const res = await client.callTool({
383
+ name: 'call_tool_chain',
384
+ arguments: { code: "return THIRD_PARTY.search({ body: { query: 'x' } });" },
385
+ });
386
+ const text = toolText(res);
387
+ expect(text).toContain('The arguments do not match the \\"search\\" tool.');
388
+ expect(text).toContain('This tool takes its arguments at the top level');
389
+ expect(platform.searchCalls).toEqual([]);
390
+ });
391
+ });
392
+
393
+ describe('the REST route answers every caller the same way', () => {
394
+ it('refuses a missing argument with the 400 and the interface, and runs nothing', async () => {
395
+ const platform = await startPlatform();
396
+ const res = await callRoute(platform.baseUrl, 'ask', { sessionId: 's' });
397
+ expect(res.status).toBe(400);
398
+ const body = (await res.json()) as { error: string; kind: string };
399
+ expect(body.kind).toBe('arguments-do-not-match');
400
+ expect(body.error).toContain('The arguments do not match the "ask" tool.');
401
+ expect(body.error).toContain('"prompt" is required, and was not given.');
402
+ expect(body.error).toContain('prompt (string, required) — What to ask.');
403
+ expect(body.error.split('\n').pop()).toBe('Call: KNOWLEDGE_BASE.ask({ body: { prompt: "..." } })');
404
+ expect(platform.askCalls).toEqual([]);
405
+ });
406
+
407
+ it('gives an MCP caller and a REST caller the SAME answer for the same wrong arguments', async () => {
408
+ const platform = await startPlatform();
409
+ const client = await connect(platform.baseUrl);
410
+ const overMcp = toolText(await client.callTool({ name: 'ask', arguments: { body: { sessionId: 's' } } }));
411
+ const overRest = (await (await callRoute(platform.baseUrl, 'ask', { sessionId: 's' })).json()) as {
412
+ error: string;
413
+ kind: string;
414
+ };
415
+ // The MCP layer shapes nothing: the route's message and the route's
416
+ // machine-readable fields come through as they are, under the one prefix
417
+ // dispatch puts on EVERY tool failure alike (a missing branch included),
418
+ // which says which tool it was.
419
+ expect(overMcp).toBe(`The "ask" tool failed: ${overRest.error} {"kind":"${overRest.kind}"}`);
420
+ expect(overMcp).toContain(overRest.error);
421
+ });
422
+
423
+ it('names an argument the tool does not have, and one of the wrong type', async () => {
424
+ const platform = await startPlatform();
425
+ const unknown = (await (await callRoute(platform.baseUrl, 'ask', { prompt: 'p', nope: 1 })).json()) as {
426
+ error: string;
427
+ };
428
+ expect(unknown.error).toContain('"nope" is not an argument of this tool.');
429
+ const wrongType = (await (await callRoute(platform.baseUrl, 'ask', { prompt: 7 })).json()) as { error: string };
430
+ expect(wrongType.error).toContain('"prompt" must be string, but integer was given.');
431
+ expect(platform.askCalls).toEqual([]);
432
+ });
433
+
434
+ it('keeps the refusal for a missing branch, word for word', async () => {
435
+ const platform = await startPlatform();
436
+ const res = await callRoute(platform.baseUrl, 'read_file', { path: 'a.md' });
437
+ expect(res.status).toBe(400);
438
+ const body = (await res.json()) as { error: string; kind: string };
439
+ expect(body.kind).toBe('branch-required');
440
+ expect(body.error).toBe(BRANCH_REQUIRED_MESSAGE);
441
+ });
442
+
443
+ it('tells an agent that passed the arguments flat that they go under `body`', async () => {
444
+ const platform = await startPlatform();
445
+ // What the http protocol produces for a flat call to a tool whose
446
+ // `body_field` is `body`: an empty body, every argument in the query.
447
+ const res = await fetch(`${platform.baseUrl}/api/agent/tools/ask?prompt=hi`, {
448
+ method: 'POST',
449
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` },
450
+ body: JSON.stringify({}),
451
+ });
452
+ expect(res.status).toBe(400);
453
+ const body = (await res.json()) as { error: string; kind: string };
454
+ expect(body.kind).toBe('arguments-do-not-match');
455
+ expect(body.error.split('\n')[1]).toBe('This tool takes its arguments under "body", not at the top level.');
456
+ expect(platform.askCalls).toEqual([]);
457
+ });
458
+
459
+ it('passes a matching call on with exactly the arguments that were sent', async () => {
460
+ const platform = await startPlatform();
461
+ const res = await callRoute(platform.baseUrl, 'ask', { prompt: 'hello', sessionId: 's-1' });
462
+ expect(res.status).toBe(200);
463
+ expect(await res.json()).toEqual({ text: 'echo: hello' });
464
+ expect(platform.askCalls).toEqual([{ prompt: 'hello', sessionId: 's-1' }]);
465
+ });
466
+ });
467
+
468
+ describe('a call that matches', () => {
469
+ it('reaches the tool with exactly the arguments that were sent', async () => {
470
+ const platform = await startPlatform();
471
+ const client = await connect(platform.baseUrl);
472
+ const res = await client.callTool({ name: 'THIRD_PARTY_search', arguments: { query: 'hello', limit: 2 } });
473
+ expect(res.isError).toBeFalsy();
474
+ expect(platform.searchCalls).toHaveLength(1);
475
+ // Both arguments arrive in the place this tool's own template puts them:
476
+ // the query string, since it names no `body_field` — which is why `limit`
477
+ // reads back as the string "2" (query-string coercion by the transport,
478
+ // not by the check). Nothing was added, dropped or renamed on the way.
479
+ expect(platform.searchCalls[0].query).toEqual({ query: 'hello', limit: '2' });
480
+ });
481
+
482
+ it('runs the same call from inside a chain', async () => {
483
+ const platform = await startPlatform();
484
+ const client = await connect(platform.baseUrl);
485
+ const res = await client.callTool({
486
+ name: 'call_tool_chain',
487
+ arguments: { code: "return THIRD_PARTY.search({ query: 'chained' });" },
488
+ });
489
+ expect(JSON.parse(toolText(res)).result).toEqual({ hits: [] });
490
+ expect(platform.searchCalls[0].query).toEqual({ query: 'chained' });
491
+ });
492
+
493
+ it('serves the example in `tools_info` too, so a chain is written from it', async () => {
494
+ const { baseUrl } = await startPlatform();
495
+ const client = await connect(baseUrl);
496
+ const res = await client.callTool({ name: 'tools_info', arguments: { tool_names: ['THIRD_PARTY.search'] } });
497
+ expect(JSON.parse(toolText(res)).interfaces).toContain('Call: THIRD_PARTY.search({ query: "..." })');
498
+ });
499
+ });
@@ -87,7 +87,8 @@ export function createMcpAuthMiddleware(
87
87
  // `externalApiKeyId` unset and are not metered.
88
88
  const resolved = await externalApiKeyService.verifyAndLoadToken(token);
89
89
  if (!resolved) {
90
- rejectConnectionKey(res);
90
+ const switchedOff = (await externalApiKeyService.isKeyOfSwitchedOffAccount?.(token)) ?? false;
91
+ rejectConnectionKey(res, { switchedOff });
91
92
  return;
92
93
  }
93
94
  req.userId = resolved.user.id;
@@ -8,6 +8,8 @@ import {
8
8
  ListToolsRequestSchema,
9
9
  ListPromptsRequestSchema,
10
10
  GetPromptRequestSchema,
11
+ ListResourcesRequestSchema,
12
+ ReadResourceRequestSchema,
11
13
  McpError,
12
14
  ErrorCode,
13
15
  isInitializeRequest,
@@ -39,6 +41,9 @@ import {
39
41
  retiredToolMessage,
40
42
  needsAuthorizationResult,
41
43
  skillPromptText,
44
+ toListedResource,
45
+ toReadResourceResult,
46
+ type McpAppManifest,
42
47
  type ProxiedTool,
43
48
  type SkillSummary,
44
49
  type LoadedSkill,
@@ -315,6 +320,18 @@ export class McpService {
315
320
  // of a request that arrived with a key or an agent connection goes
316
321
  // through it. Optional like the others; without it nothing is recorded.
317
322
  private readonly auditRecorder?: IAgentEventRecorder,
323
+ /**
324
+ * The MCP Apps this deployment serves — the views a tool's `_meta` names
325
+ * and `resources/list` / `resources/read` answer for.
326
+ *
327
+ * Optional, and absence is a real state rather than a test convenience:
328
+ * with no manifest the endpoint declares no `resources` capability and
329
+ * every tool is listed exactly as before, which is also what a
330
+ * deployment whose packaged view could not be read must look like. A
331
+ * `resourceUri` advertised without a resource behind it would have a
332
+ * host preload a failure and show an empty frame.
333
+ */
334
+ private readonly mcpApps?: { manifest(): Promise<McpAppManifest> },
318
335
  ) {
319
336
  this.downstream = new DownstreamPool<PooledDownstream>({
320
337
  ...opts.downstreamPool,
@@ -421,10 +438,24 @@ export class McpService {
421
438
  }));
422
439
 
423
440
  const initializing = (Array.isArray(messages) ? messages : [messages]).some((m) => isInitializeRequest(m));
441
+ // The deployment's MCP Apps, read once per request. Declaring the
442
+ // `resources` capability is what permits the two handlers below, and a
443
+ // deployment with no view must not declare it — a client would then list
444
+ // resources and be told the method does not exist.
445
+ const apps = this.mcpApps ? await this.mcpApps.manifest().catch(() => null) : null;
446
+ const servesApps = (apps?.resources.length ?? 0) > 0;
424
447
  const server = new Server(
425
448
  { name: 'bevel-mcp', version: '0.1.0' },
426
449
  {
427
- capabilities: { tools: {}, prompts: {} },
450
+ capabilities: {
451
+ tools: {},
452
+ prompts: {},
453
+ // Resources exist here for ONE purpose: serving the MCP App views
454
+ // the tools carry. The knowledge base's own files are not
455
+ // resources — they are what the file TOOLS read, under the access
456
+ // rules, which a resource listing has no way to express.
457
+ ...(servesApps ? { resources: {} } : {}),
458
+ },
428
459
  // `instructions` rides the initialize result; clients that honour it
429
460
  // place the text in the model's system prompt without the model acting.
430
461
  ...(initializing ? { instructions: (await agentInstructions()).instructions } : {}),
@@ -464,7 +495,12 @@ export class McpService {
464
495
  const direct: McpTool[] = [];
465
496
  const examplePool: ProxiedTool[] = [];
466
497
  for (const t of listed) {
467
- const entry = toListedTool(t); // logs its own reason on a name/schema drop
498
+ // The MCP App view this tool's result renders in, by tool name. Not
499
+ // part of the UTCP manual the tool was discovered from (UTCP has no
500
+ // place for it), so it is joined on here — the one point where a
501
+ // discovered tool becomes a listing entry.
502
+ const ui = apps && Object.hasOwn(apps.tools, t.mcpName) ? apps.tools[t.mcpName] : undefined;
503
+ const entry = toListedTool(ui ? { ...t, ui } : t); // logs its own reason on a name/schema drop
468
504
  if (!entry) {
469
505
  dropped.push(t.mcpName);
470
506
  continue;
@@ -531,6 +567,25 @@ export class McpService {
531
567
  };
532
568
  });
533
569
 
570
+ // The MCP App views. Registered only when this deployment serves one, so
571
+ // the handlers and the declared capability agree.
572
+ if (servesApps && apps) {
573
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
574
+ resources: apps.resources.map((r) => toListedResource(r)),
575
+ }));
576
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
577
+ const resource = apps.resources.find((r) => r.uri === request.params.uri);
578
+ if (!resource) {
579
+ throw new McpError(
580
+ ErrorCode.InvalidParams,
581
+ `No resource at ${request.params.uri}. This endpoint serves the MCP App views its tools ` +
582
+ `name and nothing else: ${apps.resources.map((r) => r.uri).join(', ')}.`,
583
+ );
584
+ }
585
+ return toReadResourceResult(resource);
586
+ });
587
+ }
588
+
534
589
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
535
590
  const { client, tools, unavailable, catalogNames, hidden: hiddenTools } = await requestSurface();
536
591
  const toolName = request.params.name;
@@ -610,7 +665,10 @@ export class McpService {
610
665
  if (audit) await audit.denied(proxied.utcpName, args);
611
666
  return needsAuth;
612
667
  }
613
- const run = () => this.dispatch(client, proxied, request, extra);
668
+ // A tool with an MCP App view answers structured content too — the
669
+ // view reads its fields from there.
670
+ const structured = apps !== null && Object.hasOwn(apps.tools, proxied.mcpName);
671
+ const run = () => this.dispatch(client, proxied, request, extra, structured);
614
672
  return audit ? audit.call(proxied.utcpName, args, run, isErrorResult) : run();
615
673
  });
616
674
 
@@ -1362,17 +1420,23 @@ export class McpService {
1362
1420
  // payload without a `progressToken` is accepted — same approach the prior
1363
1421
  // handler used; the strict ServerNotification type requires the token.
1364
1422
  extra: { sendNotification: (n: any) => Promise<void> },
1423
+ structured = false,
1365
1424
  ): Promise<CallToolResult> {
1366
1425
  const progressToken = request.params._meta?.progressToken;
1367
- return dispatchToolCall(client, tool, request.params.arguments ?? {}, (progress, message) =>
1368
- extra.sendNotification({
1369
- method: 'notifications/progress',
1370
- params: {
1371
- ...(progressToken !== undefined ? { progressToken } : {}),
1372
- progress,
1373
- message,
1374
- },
1375
- }),
1426
+ return dispatchToolCall(
1427
+ client,
1428
+ tool,
1429
+ request.params.arguments ?? {},
1430
+ (progress, message) =>
1431
+ extra.sendNotification({
1432
+ method: 'notifications/progress',
1433
+ params: {
1434
+ ...(progressToken !== undefined ? { progressToken } : {}),
1435
+ progress,
1436
+ message,
1437
+ },
1438
+ }),
1439
+ { structured },
1376
1440
  );
1377
1441
  }
1378
1442
 
@@ -1446,6 +1510,11 @@ function templateFingerprint(template: CallTemplate): string {
1446
1510
  * until the consumer finishes or abandons it (`for await` returns the
1447
1511
  * generator, which runs the `finally`).
1448
1512
  *
1513
+ * No guard is re-installed over this wrapper, and none is needed: a routed call
1514
+ * is made on the POOLED client, which had the guards installed on it when its
1515
+ * own manual was registered (`registerManual`). Nothing in this module checks a
1516
+ * call or shapes its failure — it forwards the call and returns the answer.
1517
+ *
1449
1518
  * A call the downstream refuses for its token (401 / `invalid_token`) goes to
1450
1519
  * the route's `afterFailure`, which may refresh the token and ask for ONE retry
1451
1520
  * on a fresh connection. Retrying is safe for the same reason session recovery