@bevel-software/platform-core-backend 0.33.0 → 0.35.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 (602) hide show
  1. package/agent-guide/access-control.md +146 -289
  2. package/agent-guide/conventions.md +23 -25
  3. package/agent-guide/directory-structure.md +72 -120
  4. package/agent-guide/drafts-and-change-requests.md +92 -0
  5. package/agent-guide/for-git-users.md +47 -0
  6. package/agent-guide/glossary.md +36 -0
  7. package/agent-guide/how-agents-find-things.md +49 -0
  8. package/agent-guide/html-views.md +239 -8
  9. package/agent-guide/new-knowledge-base.md +9 -8
  10. package/agent-guide/skills.md +67 -42
  11. package/agent-guide/start-here.md +65 -0
  12. package/agent-guide/tool-manuals.md +133 -155
  13. package/agent-guide/tools.md +67 -0
  14. package/agent-guide/where-a-new-file-goes.md +45 -28
  15. package/dist/core/catalog-revision.d.ts +12 -1
  16. package/dist/core/catalog-revision.d.ts.map +1 -1
  17. package/dist/core/catalog-revision.js +23 -3
  18. package/dist/core/catalog-revision.js.map +1 -1
  19. package/dist/core/core-ports.d.ts +41 -0
  20. package/dist/core/core-ports.d.ts.map +1 -1
  21. package/dist/core/core-ports.js.map +1 -1
  22. package/dist/core/create-core-server.d.ts.map +1 -1
  23. package/dist/core/create-core-server.js +15 -8
  24. package/dist/core/create-core-server.js.map +1 -1
  25. package/dist/core/create-core-services.d.ts +22 -5
  26. package/dist/core/create-core-services.d.ts.map +1 -1
  27. package/dist/core/create-core-services.js +44 -16
  28. package/dist/core/create-core-services.js.map +1 -1
  29. package/dist/core/lifecycle.d.ts +16 -0
  30. package/dist/core/lifecycle.d.ts.map +1 -1
  31. package/dist/core/lifecycle.js +12 -2
  32. package/dist/core/lifecycle.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/modules/access/access-control.interface.d.ts +39 -9
  37. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.d.ts +15 -3
  39. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.js +239 -75
  41. package/dist/modules/access/access-control.service.js.map +1 -1
  42. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  43. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  44. package/dist/modules/access/access-mutation.service.js +48 -51
  45. package/dist/modules/access/access-mutation.service.js.map +1 -1
  46. package/dist/modules/access/access-view.d.ts.map +1 -1
  47. package/dist/modules/access/access-view.js +12 -11
  48. package/dist/modules/access/access-view.js.map +1 -1
  49. package/dist/modules/access/access.routes.d.ts +3 -0
  50. package/dist/modules/access/access.routes.d.ts.map +1 -1
  51. package/dist/modules/access/access.routes.js +58 -33
  52. package/dist/modules/access/access.routes.js.map +1 -1
  53. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  54. package/dist/modules/access/groups-admin.routes.js +4 -2
  55. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  56. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  57. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  58. package/dist/modules/access/groups-admin.service.js +80 -38
  59. package/dist/modules/access/groups-admin.service.js.map +1 -1
  60. package/dist/modules/access/groups-edit.d.ts +1 -1
  61. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  62. package/dist/modules/access/groups-edit.js +1 -1
  63. package/dist/modules/access/groups-edit.js.map +1 -1
  64. package/dist/modules/access/index.d.ts +1 -1
  65. package/dist/modules/access/index.d.ts.map +1 -1
  66. package/dist/modules/access/reference-scan.d.ts +116 -24
  67. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  68. package/dist/modules/access/reference-scan.js +305 -44
  69. package/dist/modules/access/reference-scan.js.map +1 -1
  70. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  71. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  72. package/dist/modules/access/roles-admin.service.js +32 -23
  73. package/dist/modules/access/roles-admin.service.js.map +1 -1
  74. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  75. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  76. package/dist/modules/access-model/access-grammar.js +60 -20
  77. package/dist/modules/access-model/access-grammar.js.map +1 -1
  78. package/dist/modules/access-model/access-splice.d.ts +22 -18
  79. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  80. package/dist/modules/access-model/access-splice.js +20 -14
  81. package/dist/modules/access-model/access-splice.js.map +1 -1
  82. package/dist/modules/access-model/group-files.d.ts +1 -0
  83. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  84. package/dist/modules/access-model/group-files.js +5 -1
  85. package/dist/modules/access-model/group-files.js.map +1 -1
  86. package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
  87. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  88. package/dist/modules/agent-guide/agent-guide.js +103 -13
  89. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  90. package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
  91. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  92. package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
  93. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  94. package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
  95. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
  96. package/dist/modules/agent-guide/guide-budget.js +67 -0
  97. package/dist/modules/agent-guide/guide-budget.js.map +1 -0
  98. package/dist/modules/agent-guide/index.d.ts +2 -1
  99. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  100. package/dist/modules/agent-guide/index.js +2 -1
  101. package/dist/modules/agent-guide/index.js.map +1 -1
  102. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  103. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  104. package/dist/modules/agent-instructions/compose.js +7 -1
  105. package/dist/modules/agent-instructions/compose.js.map +1 -1
  106. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  107. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  108. package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
  109. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  110. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  111. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  112. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  113. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  114. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  115. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  116. package/dist/modules/agent-scope/connection-scope.js +165 -0
  117. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  118. package/dist/modules/agent-scope/index.d.ts +3 -0
  119. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  120. package/dist/modules/agent-scope/index.js +3 -0
  121. package/dist/modules/agent-scope/index.js.map +1 -0
  122. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  123. package/dist/modules/audit/agent-audit.service.js +2 -0
  124. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  125. package/dist/modules/audit/audit.contract.d.ts +6 -0
  126. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  127. package/dist/modules/audit/audit.contract.js.map +1 -1
  128. package/dist/modules/auth/account-admission.d.ts +12 -0
  129. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  130. package/dist/modules/auth/account-admission.js +15 -0
  131. package/dist/modules/auth/account-admission.js.map +1 -1
  132. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  133. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  134. package/dist/modules/auth/auth.middleware.js +42 -4
  135. package/dist/modules/auth/auth.middleware.js.map +1 -1
  136. package/dist/modules/auth/auth.routes.d.ts +13 -9
  137. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  138. package/dist/modules/auth/auth.routes.js +35 -29
  139. package/dist/modules/auth/auth.routes.js.map +1 -1
  140. package/dist/modules/auth/auth.service.d.ts +55 -6
  141. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  142. package/dist/modules/auth/auth.service.js +128 -34
  143. package/dist/modules/auth/auth.service.js.map +1 -1
  144. package/dist/modules/auth/index.d.ts +1 -1
  145. package/dist/modules/auth/index.d.ts.map +1 -1
  146. package/dist/modules/auth/index.js +1 -1
  147. package/dist/modules/auth/index.js.map +1 -1
  148. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  149. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  150. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  151. package/dist/modules/code-mode/code-mode.tool.js +2 -2
  152. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  153. package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
  154. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  155. package/dist/modules/connection-probe/connection-probe.service.js +1 -38
  156. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  157. package/dist/modules/database/core-schema.d.ts +68 -0
  158. package/dist/modules/database/core-schema.d.ts.map +1 -1
  159. package/dist/modules/database/core-schema.js +33 -0
  160. package/dist/modules/database/core-schema.js.map +1 -1
  161. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  162. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  163. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  164. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  165. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  166. package/dist/modules/embed/embed.tools.js +5 -0
  167. package/dist/modules/embed/embed.tools.js.map +1 -1
  168. package/dist/modules/kb-fs/index.d.ts +2 -0
  169. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  170. package/dist/modules/kb-fs/index.js +1 -0
  171. package/dist/modules/kb-fs/index.js.map +1 -1
  172. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  173. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  174. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  175. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  176. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  177. package/dist/modules/kb-fs/repo-path.js +22 -7
  178. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  179. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  180. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  181. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  182. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  183. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  184. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  185. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  186. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  187. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  188. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  189. package/dist/modules/mcp/mcp.routes.js +39 -4
  190. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  191. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  192. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  193. package/dist/modules/mcp/mcp.service.js +74 -48
  194. package/dist/modules/mcp/mcp.service.js.map +1 -1
  195. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  196. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  197. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  198. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  199. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  200. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  201. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  202. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  203. package/dist/modules/plugins/index.d.ts +1 -1
  204. package/dist/modules/plugins/index.d.ts.map +1 -1
  205. package/dist/modules/plugins/index.js.map +1 -1
  206. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  207. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  208. package/dist/modules/plugins/plugin-links.js.map +1 -1
  209. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  210. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  211. package/dist/modules/plugins/plugins.routes.js +32 -5
  212. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  213. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
  214. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  215. package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
  216. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  217. package/dist/modules/secrets-vault/index.d.ts +1 -1
  218. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  219. package/dist/modules/secrets-vault/index.js +1 -1
  220. package/dist/modules/secrets-vault/index.js.map +1 -1
  221. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
  222. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  223. package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
  224. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  225. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  226. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  227. package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
  228. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  229. package/dist/modules/settings/oidc-check.d.ts.map +1 -1
  230. package/dist/modules/settings/oidc-check.js +5 -17
  231. package/dist/modules/settings/oidc-check.js.map +1 -1
  232. package/dist/modules/skills/index.d.ts +3 -3
  233. package/dist/modules/skills/index.d.ts.map +1 -1
  234. package/dist/modules/skills/index.js +0 -2
  235. package/dist/modules/skills/index.js.map +1 -1
  236. package/dist/modules/skills/skills.contract.d.ts +16 -0
  237. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  238. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  239. package/dist/modules/skills/skills.service.js +6 -1
  240. package/dist/modules/skills/skills.service.js.map +1 -1
  241. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  242. package/dist/modules/skills/skills.tools.js +25 -13
  243. package/dist/modules/skills/skills.tools.js.map +1 -1
  244. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  245. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  246. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  247. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  248. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  249. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  250. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  251. package/dist/modules/tool-auth/index.d.ts +1 -1
  252. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  253. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  254. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  255. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  256. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  257. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  258. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  259. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  260. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  261. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  262. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  263. package/dist/modules/tool-helpers/tool-context.js +3 -0
  264. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  265. package/dist/modules/tool-helpers/tool-def.js +1 -1
  266. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  267. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  268. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  269. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  270. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  271. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  272. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  273. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  274. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  275. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  276. package/dist/modules/tool-helpers/validate-token.js +1 -1
  277. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  278. package/dist/modules/tool-manuals/index.d.ts +3 -3
  279. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  280. package/dist/modules/tool-manuals/index.js +0 -2
  281. package/dist/modules/tool-manuals/index.js.map +1 -1
  282. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
  283. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  284. package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
  285. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  286. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
  287. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  288. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  289. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  290. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  291. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  292. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
  293. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  294. package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
  295. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  296. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
  297. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  298. package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
  299. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  300. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  301. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  302. package/dist/modules/tool-registry/manual.routes.js +27 -7
  303. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  304. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  305. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  306. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  307. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  308. package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
  309. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  310. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  311. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  312. package/dist/modules/workflow/git/git.service.js +20 -35
  313. package/dist/modules/workflow/git/git.service.js.map +1 -1
  314. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  315. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  316. package/dist/modules/workflow/workflow.service.js +3 -0
  317. package/dist/modules/workflow/workflow.service.js.map +1 -1
  318. package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
  319. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  320. package/dist/modules/workspace/agent-access.gate.js +1 -1
  321. package/dist/modules/workspace/agent-access.gate.js.map +1 -1
  322. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  323. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  324. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  325. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  326. package/dist/modules/workspace/index.d.ts +8 -0
  327. package/dist/modules/workspace/index.d.ts.map +1 -1
  328. package/dist/modules/workspace/index.js +8 -0
  329. package/dist/modules/workspace/index.js.map +1 -1
  330. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  331. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  332. package/dist/modules/workspace/nul-scan.js +55 -0
  333. package/dist/modules/workspace/nul-scan.js.map +1 -0
  334. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  335. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  336. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  337. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  338. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  339. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  340. package/dist/modules/workspace/ripgrep-search.js +657 -0
  341. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  342. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  343. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  344. package/dist/modules/workspace/ripgrep-version.js +31 -0
  345. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  346. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  347. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  348. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  349. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  350. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  351. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  352. package/dist/modules/workspace/worker-pool.js.map +1 -1
  353. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  354. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  355. package/dist/modules/workspace/workspace-tmp.js +435 -0
  356. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  357. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  358. package/dist/modules/workspace/workspace.routes.js +5 -26
  359. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  360. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  361. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  362. package/dist/modules/workspace/workspace.service.js +86 -6
  363. package/dist/modules/workspace/workspace.service.js.map +1 -1
  364. package/dist/modules/workspace/workspace.tools.d.ts +3 -2
  365. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  366. package/dist/modules/workspace/workspace.tools.js +531 -137
  367. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  368. package/dist/shared/oauth-token-error.d.ts +23 -0
  369. package/dist/shared/oauth-token-error.d.ts.map +1 -0
  370. package/dist/shared/oauth-token-error.js +47 -0
  371. package/dist/shared/oauth-token-error.js.map +1 -0
  372. package/dist/shared/path-order.d.ts +28 -8
  373. package/dist/shared/path-order.d.ts.map +1 -1
  374. package/dist/shared/path-order.js +37 -8
  375. package/dist/shared/path-order.js.map +1 -1
  376. package/dist/shared/read-capped.d.ts +19 -0
  377. package/dist/shared/read-capped.d.ts.map +1 -0
  378. package/dist/shared/read-capped.js +45 -0
  379. package/dist/shared/read-capped.js.map +1 -0
  380. package/migrations/0022_session_version.sql +6 -0
  381. package/migrations/0023_agent_path.sql +3 -0
  382. package/migrations/meta/0022_snapshot.json +2360 -0
  383. package/migrations/meta/0023_snapshot.json +2378 -0
  384. package/migrations/meta/_journal.json +14 -0
  385. package/package.json +4 -3
  386. package/src/__tests__/hexis-catalog.ts +144 -0
  387. package/src/__tests__/pg-scratch.test.ts +69 -0
  388. package/src/__tests__/pg-scratch.ts +31 -0
  389. package/src/__tests__/real-disk.ts +21 -0
  390. package/src/__tests__/slow-ripgrep.ts +65 -0
  391. package/src/__tests__/tokens.ts +19 -0
  392. package/src/__tests__/tool-manuals.ts +32 -0
  393. package/src/__tests__/tool-wiring.ts +25 -0
  394. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  395. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  396. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  397. package/src/core/__tests__/gated-boot.test.ts +8 -1
  398. package/src/core/__tests__/lifecycle.test.ts +46 -13
  399. package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
  400. package/src/core/catalog-revision.ts +24 -2
  401. package/src/core/core-ports.ts +42 -0
  402. package/src/core/create-core-server.ts +14 -1
  403. package/src/core/create-core-services.ts +62 -15
  404. package/src/core/lifecycle.ts +26 -2
  405. package/src/index.ts +1 -1
  406. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  407. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  408. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  409. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  410. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  411. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  412. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  413. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  414. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  415. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  416. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  417. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  418. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  419. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  420. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  421. package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
  422. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  423. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  424. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  425. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  426. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  427. package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
  428. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  429. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  430. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  431. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  432. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  433. package/src/modules/access/access-control.interface.ts +43 -10
  434. package/src/modules/access/access-control.service.ts +250 -72
  435. package/src/modules/access/access-mutation.service.ts +48 -53
  436. package/src/modules/access/access-view.ts +12 -10
  437. package/src/modules/access/access.routes.ts +66 -37
  438. package/src/modules/access/groups-admin.routes.ts +4 -2
  439. package/src/modules/access/groups-admin.service.ts +114 -44
  440. package/src/modules/access/groups-edit.ts +1 -1
  441. package/src/modules/access/index.ts +1 -1
  442. package/src/modules/access/reference-scan.ts +319 -46
  443. package/src/modules/access/roles-admin.service.ts +43 -26
  444. package/src/modules/access/synced-groups-writer.ts +1 -1
  445. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  446. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  447. package/src/modules/access-model/access-grammar.ts +65 -20
  448. package/src/modules/access-model/access-splice.ts +31 -23
  449. package/src/modules/access-model/group-files.ts +5 -0
  450. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
  451. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
  452. package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
  453. package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
  454. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
  455. package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
  456. package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
  457. package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
  458. package/src/modules/agent-guide/agent-guide.ts +120 -13
  459. package/src/modules/agent-guide/guide-budget.ts +73 -0
  460. package/src/modules/agent-guide/index.ts +12 -0
  461. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
  462. package/src/modules/agent-instructions/compose.ts +7 -1
  463. package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
  464. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  465. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  466. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  467. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  468. package/src/modules/agent-scope/connection-scope.ts +199 -0
  469. package/src/modules/agent-scope/index.ts +15 -0
  470. package/src/modules/audit/agent-audit.service.ts +2 -0
  471. package/src/modules/audit/audit.contract.ts +6 -0
  472. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  473. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  474. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  475. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  476. package/src/modules/auth/account-admission.ts +19 -0
  477. package/src/modules/auth/auth.middleware.ts +45 -4
  478. package/src/modules/auth/auth.routes.ts +46 -29
  479. package/src/modules/auth/auth.service.ts +135 -34
  480. package/src/modules/auth/index.ts +2 -0
  481. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  482. package/src/modules/code-mode/code-mode.tool.ts +2 -2
  483. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
  484. package/src/modules/connection-probe/connection-probe.service.ts +1 -34
  485. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  486. package/src/modules/database/core-schema.ts +33 -0
  487. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  488. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  489. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  490. package/src/modules/embed/embed.tools.ts +5 -0
  491. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  492. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  493. package/src/modules/kb-fs/index.ts +2 -0
  494. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  495. package/src/modules/kb-fs/repo-path.ts +33 -7
  496. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  497. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  498. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  499. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  500. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  501. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  502. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  503. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  504. package/src/modules/mcp/mcp.routes.ts +39 -3
  505. package/src/modules/mcp/mcp.service.ts +112 -52
  506. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  507. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  508. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  509. package/src/modules/plugins/index.ts +1 -1
  510. package/src/modules/plugins/plugin-links.ts +19 -2
  511. package/src/modules/plugins/plugins.routes.ts +35 -4
  512. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  513. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
  514. package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
  515. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
  516. package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
  517. package/src/modules/secrets-vault/index.ts +1 -1
  518. package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
  519. package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
  520. package/src/modules/settings/oidc-check.ts +5 -18
  521. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  522. package/src/modules/skills/index.ts +7 -2
  523. package/src/modules/skills/skills.contract.ts +17 -0
  524. package/src/modules/skills/skills.service.ts +5 -1
  525. package/src/modules/skills/skills.tools.ts +45 -14
  526. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  527. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  528. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  529. package/src/modules/tool-auth/index.ts +1 -1
  530. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  531. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  532. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
  533. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  534. package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
  535. package/src/modules/tool-helpers/tool-context.ts +10 -0
  536. package/src/modules/tool-helpers/tool-def.ts +1 -1
  537. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  538. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  539. package/src/modules/tool-helpers/validate-token.ts +4 -2
  540. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  541. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
  542. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  543. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
  544. package/src/modules/tool-manuals/index.ts +6 -2
  545. package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
  546. package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
  547. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  548. package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
  549. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
  550. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
  551. package/src/modules/tool-registry/manual.routes.ts +26 -5
  552. package/src/modules/tool-registry/tool.contract.ts +7 -0
  553. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  554. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  555. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  556. package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
  557. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  558. package/src/modules/workflow/git/git.service.ts +29 -48
  559. package/src/modules/workflow/workflow.service.ts +5 -0
  560. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  561. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  562. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  563. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  564. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  565. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  566. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  567. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  568. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
  569. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  570. package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
  571. package/src/modules/workspace/agent-access.gate.ts +1 -1
  572. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  573. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  574. package/src/modules/workspace/index.ts +20 -0
  575. package/src/modules/workspace/nul-scan.ts +50 -0
  576. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  577. package/src/modules/workspace/ripgrep-search.ts +715 -0
  578. package/src/modules/workspace/ripgrep-version.ts +35 -0
  579. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  580. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  581. package/src/modules/workspace/worker-pool.ts +3 -4
  582. package/src/modules/workspace/workspace-tmp.ts +457 -0
  583. package/src/modules/workspace/workspace.routes.ts +6 -27
  584. package/src/modules/workspace/workspace.service.ts +92 -15
  585. package/src/modules/workspace/workspace.tools.ts +574 -176
  586. package/src/shared/__tests__/read-capped.test.ts +68 -0
  587. package/src/shared/oauth-token-error.ts +46 -0
  588. package/src/shared/path-order.ts +37 -8
  589. package/src/shared/read-capped.ts +40 -0
  590. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  591. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  592. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  593. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  594. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  595. package/agent-guide/finding-things.md +0 -7
  596. package/agent-guide/introduction.md +0 -27
  597. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  598. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  599. package/dist/modules/workspace/bounded-matcher.js +0 -200
  600. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  601. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  602. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -0,0 +1,137 @@
1
+ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { eq } from 'drizzle-orm';
4
+ import { migrate } from 'drizzle-orm/node-postgres/migrator';
5
+ import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
6
+ import { BevelOAuthProvider } from '../oauth/bevel-oauth-provider.js';
7
+ import { ExternalApiKeyService } from '../../tool-auth/external-api-key.service.js';
8
+ import { closeDb, createDb, type Database } from '../../database/connection.js';
9
+ import { agentConnections, externalApiKeys, oauthClients, users } from '../../database/core-schema.js';
10
+ import { coreMigrationsDir } from '../../../assets.js';
11
+ import { PG_ADMIN_URL, PG_TEST_TIMEOUT, dropScratchDatabases, scratchDatabaseUrl } from '../../../__tests__/pg-scratch.js';
12
+
13
+ /**
14
+ * The "Connect as" choice, through the real intermediary it is stored in: a
15
+ * migrated Postgres. The consent's choice goes onto the one-time code at
16
+ * Finish, crosses the token exchange onto the agent connection, and comes back
17
+ * out of every access-token verification; a key's choice goes onto its row at
18
+ * mint and comes back out of every key verification. Driven through the
19
+ * provider's and the key service's public surfaces only. Skipped without
20
+ * `TEST_DATABASE_URL`, as every `*.pg.test.ts` is.
21
+ */
22
+
23
+ const PREFIX = 'bevel-mcp_';
24
+ const REDIRECT = 'https://agent.example.com/callback';
25
+
26
+ describe.skipIf(!PG_ADMIN_URL)('connecting as an agent, on a real Postgres', () => {
27
+ let db: Database;
28
+ let provider: BevelOAuthProvider;
29
+
30
+ beforeAll(async () => {
31
+ db = createDb(await scratchDatabaseUrl('hexis_connect_as'), { piiKey: randomBytes(32).toString('base64') });
32
+ await migrate(db, { migrationsFolder: coreMigrationsDir() });
33
+ provider = new BevelOAuthProvider({
34
+ db,
35
+ crypto: null,
36
+ stateSecret: 'test-state-secret',
37
+ publicFrontendUrl: 'https://app.example.com',
38
+ tokenPrefix: PREFIX,
39
+ });
40
+ vi.spyOn(console, 'log').mockImplementation(() => undefined);
41
+ }, PG_TEST_TIMEOUT);
42
+
43
+ afterAll(async () => {
44
+ vi.restoreAllMocks();
45
+ await closeDb(db);
46
+ await dropScratchDatabases();
47
+ }, PG_TEST_TIMEOUT);
48
+
49
+ async function person(tag: string): Promise<string> {
50
+ const [user] = await db
51
+ .insert(users)
52
+ .values({ email: `${tag}@example.com`, emailBidx: `${tag}@example.com`, name: tag })
53
+ .returning({ id: users.id });
54
+ return user!.id;
55
+ }
56
+
57
+ async function registeredClient(tag: string): Promise<OAuthClientInformationFull> {
58
+ const clientId = `client-${tag}-${randomBytes(4).toString('hex')}`;
59
+ await db.insert(oauthClients).values({ clientId, clientName: 'Claude', redirectUris: [REDIRECT] });
60
+ return { client_id: clientId, redirect_uris: [REDIRECT], token_endpoint_auth_method: 'none' };
61
+ }
62
+
63
+ /** Finish on the consent step with `agentPath` (undefined: the route's Everything), then the client's exchange. */
64
+ async function consent(userId: string, client: OAuthClientInformationFull, agentPath?: string | null) {
65
+ const st = { c: client.client_id, r: REDIRECT, cc: 'challenge', iat: Date.now() };
66
+ const { redirectTo } =
67
+ agentPath === undefined ? await provider.issueAuthCode(userId, st) : await provider.issueAuthCode(userId, st, agentPath);
68
+ const code = new URL(redirectTo).searchParams.get('code')!;
69
+ return provider.exchangeAuthorizationCode(client, code);
70
+ }
71
+
72
+ const connectionsOf = (userId: string) =>
73
+ db
74
+ .select({ id: agentConnections.id, agentPath: agentConnections.agentPath, revokedAt: agentConnections.revokedAt })
75
+ .from(agentConnections)
76
+ .where(eq(agentConnections.userId, userId));
77
+
78
+ it('Everything: a consent with no choice records null and verifies with none — exactly as before', async () => {
79
+ const userId = await person('everything');
80
+ const tokens = await consent(userId, await registeredClient('everything'));
81
+ expect(await connectionsOf(userId)).toEqual([{ id: expect.any(String), agentPath: null, revokedAt: null }]);
82
+ expect((await provider.verifyAccessToken(tokens.access_token)).extra?.agentPath).toBeNull();
83
+ });
84
+
85
+ it("the consent's choice round-trips to the connection row, and out of every verification", async () => {
86
+ const userId = await person('ada');
87
+ const client = await registeredClient('ada');
88
+ const tokens = await consent(userId, client, 'Agents/delivery-mechanic.agent');
89
+
90
+ const [row] = await connectionsOf(userId);
91
+ expect(row!.agentPath).toBe('Agents/delivery-mechanic.agent');
92
+ const info = await provider.verifyAccessToken(tokens.access_token);
93
+ expect(info.extra).toMatchObject({ connectionId: row!.id, agentPath: 'Agents/delivery-mechanic.agent' });
94
+
95
+ // A refresh keeps the connection's choice.
96
+ const refreshed = await provider.exchangeRefreshToken(client, tokens.refresh_token!);
97
+ expect((await provider.verifyAccessToken(refreshed.access_token)).extra?.agentPath).toBe('Agents/delivery-mechanic.agent');
98
+ });
99
+
100
+ it('re-consenting with another choice changes the live connection, and its tokens follow it', async () => {
101
+ const userId = await person('bea');
102
+ const first = await consent(userId, await registeredClient('bea'), 'Agents/a.agent');
103
+ // Claude registers afresh on every re-authorisation; the agent is one connection.
104
+ await consent(userId, await registeredClient('bea'), null);
105
+
106
+ const rows = await connectionsOf(userId);
107
+ expect(rows).toHaveLength(1);
108
+ expect(rows[0]!.agentPath).toBeNull();
109
+ // The earlier pair still verifies — and now as the connection's choice.
110
+ expect((await provider.verifyAccessToken(first.access_token)).extra?.agentPath).toBeNull();
111
+
112
+ await consent(userId, await registeredClient('bea'), 'Agents/b.agent');
113
+ expect((await connectionsOf(userId))[0]!.agentPath).toBe('Agents/b.agent');
114
+ });
115
+
116
+ it("a key's choice is stored at mint and comes back out of every verification", async () => {
117
+ const userId = await person('cy');
118
+ const keys = new ExternalApiKeyService(db, 'bevel_');
119
+ const asAgent = await keys.mint(userId, 'pipeline', { agentPath: 'Agents/ops.agent' });
120
+ const asEverything = await keys.mint(userId, 'laptop');
121
+
122
+ expect(asAgent.summary.agentPath).toBe('Agents/ops.agent');
123
+ expect(asEverything.summary.agentPath).toBeNull();
124
+ expect((await keys.verifyAndLoadToken(asAgent.plaintext))?.agentPath).toBe('Agents/ops.agent');
125
+ expect((await keys.verifyAndLoadToken(asEverything.plaintext))?.agentPath).toBeNull();
126
+
127
+ const stored = await db
128
+ .select({ label: externalApiKeys.label, agentPath: externalApiKeys.agentPath })
129
+ .from(externalApiKeys)
130
+ .where(eq(externalApiKeys.userId, userId));
131
+ expect(stored.sort((a, b) => a.label.localeCompare(b.label))).toEqual([
132
+ { label: 'laptop', agentPath: null },
133
+ { label: 'pipeline', agentPath: 'Agents/ops.agent' },
134
+ ]);
135
+ expect((await keys.listForUser(userId)).map((k) => k.agentPath).sort()).toEqual(['Agents/ops.agent', null]);
136
+ });
137
+ });
@@ -53,8 +53,8 @@ function makeExternalApiKeyService(
53
53
 
54
54
  function makeOAuthProvider(
55
55
  verify?: (t: string) => Promise<{ extra?: Record<string, unknown> }>,
56
- /** Whether an agent connection still admits requests — true unless a test says otherwise. */
57
- live?: (connectionId: string) => Promise<boolean>,
56
+ /** A live agent connection's current choice, or null when revoked — live as Everything unless a test says otherwise. */
57
+ live?: (connectionId: string) => Promise<{ agentPath: string | null } | null>,
58
58
  ) {
59
59
  return {
60
60
  looksLikeAccessToken: (t: string) => typeof t === 'string' && t.startsWith('bevel-mcp_'),
@@ -64,7 +64,7 @@ function makeOAuthProvider(
64
64
  throw new InvalidTokenError('unknown token');
65
65
  }),
66
66
  ),
67
- isConnectionLive: vi.fn(live ?? (async () => true)),
67
+ liveConnectionChoice: vi.fn(live ?? (async () => ({ agentPath: null }))),
68
68
  noteConnectionUse: vi.fn(),
69
69
  } as unknown as BevelOAuthProvider;
70
70
  }
@@ -318,10 +318,34 @@ describe('createMcpAuthMiddleware', () => {
318
318
  expect(req.agentConnectionId).toBe('conn-7');
319
319
  // Every request through the exchanged grant asks whether the
320
320
  // connection still admits one, and counts as a use of it.
321
- expect((oauth as any).isConnectionLive).toHaveBeenCalledWith('conn-7');
321
+ expect((oauth as any).liveConnectionChoice).toHaveBeenCalledWith('conn-7');
322
322
  expect((oauth as any).noteConnectionUse).toHaveBeenCalledWith('conn-7');
323
323
  });
324
324
 
325
+ it("serves an exchanged grant as its connection's CURRENT choice, not the one it was minted with", async () => {
326
+ const auth = makeAuthService();
327
+ (auth.getUserById as ReturnType<typeof vi.fn>) = vi.fn(async () => ({
328
+ id: 'user-7',
329
+ email: 'seven@example.com',
330
+ }));
331
+ // Minted as Everything; the person has since re-consented as an agent.
332
+ const everything = internal.mint({ userId: 'user-7', externalProxy: true, connectionId: 'conn-7' }, 60_000);
333
+ const narrowed = makeOAuthProvider(undefined, async () => ({ agentPath: 'Agents/narrow.agent' }));
334
+ const first = makeReqRes(`Bearer ${everything}`);
335
+ await makeMw({ internal, auth, oauth: narrowed })(first.req, first.res, first.next);
336
+ expect(first.next).toHaveBeenCalled();
337
+ expect(first.req.mcpAgentPath).toBe('Agents/narrow.agent');
338
+ // Minted as an agent; the person has since re-consented as Everything.
339
+ const asAgent = internal.mint(
340
+ { userId: 'user-7', externalProxy: true, connectionId: 'conn-7', agentPath: 'Agents/narrow.agent' },
341
+ 60_000,
342
+ );
343
+ const second = makeReqRes(`Bearer ${asAgent}`);
344
+ await makeMw({ internal, auth, oauth: makeOAuthProvider() })(second.req, second.res, second.next);
345
+ expect(second.next).toHaveBeenCalled();
346
+ expect(second.req.mcpAgentPath).toBeUndefined();
347
+ });
348
+
325
349
  it('401s an exchanged grant whose agent connection was revoked — the token is stateless, the connection is not', async () => {
326
350
  const token = internal.mint({ userId: 'user-7', externalProxy: true, connectionId: 'conn-7' }, 60_000);
327
351
  const auth = makeAuthService();
@@ -329,7 +353,7 @@ describe('createMcpAuthMiddleware', () => {
329
353
  id: 'user-7',
330
354
  email: 'seven@example.com',
331
355
  }));
332
- const oauth = makeOAuthProvider(undefined, async () => false);
356
+ const oauth = makeOAuthProvider(undefined, async () => null);
333
357
  const mw = makeMw({ internal, auth, oauth });
334
358
  const { req, res, next, status, setHeader } = makeReqRes(`Bearer ${token}`);
335
359
  await mw(req, res, next);
@@ -29,6 +29,12 @@ import type { McpCallerKind } from '../mcp-auth.middleware.js';
29
29
  */
30
30
  export const fakeAuth: RequestHandler = (req, _res, next) => {
31
31
  req.userId = (req.headers['x-test-user'] as string | undefined) ?? 'user-A';
32
+ // The signed-in person's email, for a suite whose route reads it.
33
+ const email = req.headers['x-test-email'] as string | undefined;
34
+ if (email) req.userEmail = email;
35
+ // The agent the credential connects as, as the real middleware sets it.
36
+ const agentPath = req.headers['x-test-agent-path'] as string | undefined;
37
+ if (agentPath) req.mcpAgentPath = agentPath;
32
38
  // The credential kind the real middleware would have recognised, when a
33
39
  // suite needs one (the page header is a session's alone).
34
40
  const kind = req.headers['x-test-kind'] as McpCallerKind | undefined;
@@ -48,6 +54,7 @@ export interface McpRoutesDeps {
48
54
  internalTokens?: unknown;
49
55
  oauthProvider?: unknown;
50
56
  resourceMetadataUrl?: string;
57
+ agents?: unknown;
51
58
  }
52
59
 
53
60
  /** Servers opened by `mountMcpRoutes`, closed together by `closeMountedRoutes`. */
@@ -73,6 +80,7 @@ export async function mountMcpRoutes(deps: McpRoutesDeps = {}): Promise<string>
73
80
  (deps.internalTokens ?? stub) as never,
74
81
  (deps.oauthProvider ?? stub) as never,
75
82
  deps.resourceMetadataUrl ?? '',
83
+ deps.agents as never,
76
84
  ),
77
85
  );
78
86
  const server = await new Promise<HttpServer>((resolve) => {
@@ -0,0 +1,195 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import express from 'express';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { CONNECTION_META_KEY, McpService, type McpCaller } from '../mcp.service.js';
9
+ import { SpillStore } from '../../workspace/index.js';
10
+ import { createManualRoutes } from '../../tool-registry/manual.routes.js';
11
+ import { ToolRegistry } from '../../tool-registry/tool-registry.js';
12
+ import { toolDef } from '../../tool-helpers/index.js';
13
+ import type { ConnectionScopeWire } from '../../agent-scope/index.js';
14
+
15
+ /**
16
+ * The MCP proxy's half of connecting as an agent. The surfaces behind the
17
+ * loopback do the narrowing (pinned in the agent-scope suites); what the proxy
18
+ * owns is the WORDING and the CACHE: a tool off a narrowed surface is refused
19
+ * by name and agent rather than as unknown, a connection whose file is gone is
20
+ * served an empty listing with the reason, the handshake says what the
21
+ * connection is connected as, and two connections of one person made as
22
+ * different agents never share a built surface.
23
+ *
24
+ * The loopback here answers each bearer as the agent surfaces would answer
25
+ * that credential: `bevel_all` is Everything, `bevel_narrow` an agent that
26
+ * loads `read_file` alone, `bevel_gone` one whose file was deleted.
27
+ */
28
+
29
+ const NARROW: ConnectionScopeWire = { agent: { path: 'Agents/narrow.agent', name: 'narrow' } };
30
+ const GONE: ConnectionScopeWire = {
31
+ agent: { path: 'Agents/gone.agent', name: 'Agents/gone.agent' },
32
+ unavailable: 'This connection was made as the agent file `Agents/gone.agent`, which no longer exists.',
33
+ };
34
+ const CONNECTIONS: Record<string, ConnectionScopeWire | undefined> = {
35
+ bevel_all: undefined,
36
+ bevel_narrow: NARROW,
37
+ bevel_gone: GONE,
38
+ };
39
+
40
+ let httpServer: HttpServer | undefined;
41
+ const cleanups: Array<() => Promise<void>> = [];
42
+ afterEach(async () => {
43
+ for (const c of cleanups.splice(0)) await c().catch(() => {});
44
+ if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
45
+ httpServer = undefined;
46
+ vi.restoreAllMocks();
47
+ });
48
+
49
+ async function harness() {
50
+ const state = { builds: 0, base: '' };
51
+ const connectionOf = (req: express.Request) => CONNECTIONS[(req.headers.authorization ?? '').replace(/^Bearer /, '')];
52
+ const registry = new ToolRegistry();
53
+ for (const name of ['read_file', 'edit_file']) {
54
+ registry.registerExternalTool(
55
+ toolDef({ name, description: name, path: `/api/agent/tools/${name}`, inputs: { type: 'object', properties: {} } }),
56
+ );
57
+ }
58
+ const app = express();
59
+ app.use(express.json());
60
+ app.get('/api/agent/catalog-revision', (req, res) => {
61
+ const connection = connectionOf(req);
62
+ res.json({ revision: 'rev-1', tools: 0, skills: 0, ...(connection ? { connection } : {}) });
63
+ });
64
+ app.get('/api/agent/all-tools', (req, res) => {
65
+ state.builds += 1;
66
+ const connection = connectionOf(req);
67
+ res.json({ manuals: [], ...(connection ? { connection } : {}) });
68
+ });
69
+ // The platform manual, as the agent surfaces narrow it per credential.
70
+ app.get('/api/agent/utcp', (req, res, next) => {
71
+ const connection = connectionOf(req);
72
+ if (!connection) return next();
73
+ res.json({
74
+ utcp_version: '1.1.0',
75
+ manual_version: '1.0.0',
76
+ tools: connection.unavailable
77
+ ? []
78
+ : [
79
+ {
80
+ name: 'read_file',
81
+ description: 'read_file',
82
+ inputs: { type: 'object', properties: {} },
83
+ outputs: { type: 'object', properties: {} },
84
+ tags: [],
85
+ tool_call_template: {
86
+ call_template_type: 'http',
87
+ http_method: 'POST',
88
+ url: `${state.base}/api/agent/tools/read_file`,
89
+ content_type: 'application/json',
90
+ },
91
+ },
92
+ ],
93
+ });
94
+ });
95
+ app.use('/api', createManualRoutes(registry, (_req, _res, next) => next()));
96
+ httpServer = await new Promise<HttpServer>((r) => {
97
+ const s = app.listen(0, () => r(s));
98
+ });
99
+ state.base = `http://127.0.0.1:${(httpServer.address() as { port: number }).port}`;
100
+ const mcp = new McpService({
101
+ loopbackBaseUrl: state.base,
102
+ manualName: 'KNOWLEDGE_BASE',
103
+ spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
104
+ publicFrontendUrl: 'http://localhost:5173',
105
+ });
106
+ return { state, mcp };
107
+ }
108
+
109
+ async function connect(mcp: McpService, caller: Partial<McpCaller>, initialize = false): Promise<Client> {
110
+ const message = initialize
111
+ ? { jsonrpc: '2.0', id: 0, method: 'initialize', params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 't', version: '0' } } }
112
+ : { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} };
113
+ const server = await mcp.createRequestServer(
114
+ { userId: 'user-A', tokenId: 'key-1', connectionId: null, bearer: 'bevel_all', ...caller },
115
+ message,
116
+ );
117
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
118
+ await server.connect(serverTransport);
119
+ const client = new Client({ name: 'test-client', version: '0.0.0' }, { capabilities: {} });
120
+ await client.connect(clientTransport);
121
+ cleanups.push(async () => {
122
+ await client.close();
123
+ await server.close();
124
+ });
125
+ return client;
126
+ }
127
+
128
+ const textOf = (res: Awaited<ReturnType<Client['callTool']>>) =>
129
+ (res.content as { type: string; text?: string }[]).find((c) => c.type === 'text')?.text;
130
+
131
+ describe('a narrowed connection', () => {
132
+ it('lists what its agent loads, and refuses another tool by name and agent — not as unknown', async () => {
133
+ const { mcp } = await harness();
134
+ vi.spyOn(console, 'log').mockImplementation(() => {});
135
+ const client = await connect(mcp, { bearer: 'bevel_narrow', agentPath: 'Agents/narrow.agent' });
136
+ const names = (await client.listTools()).tools.map((t) => t.name);
137
+ expect(names).toContain('read_file');
138
+ expect(names).not.toContain('edit_file');
139
+ const refused = await client.callTool({ name: 'edit_file', arguments: {} });
140
+ expect(refused.isError).toBe(true);
141
+ expect(textOf(refused)).toBe(
142
+ 'The tool "edit_file" is not available to this connection: it is connected as the agent "narrow" ' +
143
+ '(`Agents/narrow.agent`), which does not load it.',
144
+ );
145
+ });
146
+
147
+ it('Everything still answers an unknown tool as unknown', async () => {
148
+ const { mcp } = await harness();
149
+ vi.spyOn(console, 'log').mockImplementation(() => {});
150
+ const client = await connect(mcp, {});
151
+ expect((await client.listTools()).tools.map((t) => t.name)).toEqual(
152
+ expect.arrayContaining(['read_file', 'edit_file']),
153
+ );
154
+ expect(textOf(await client.callTool({ name: 'nope', arguments: {} }))).toBe('Unknown tool "nope".');
155
+ });
156
+
157
+ it('tells the agent at the handshake what it is connected as; Everything is told what it always was', async () => {
158
+ const { mcp } = await harness();
159
+ vi.spyOn(console, 'log').mockImplementation(() => {});
160
+ const narrowed = (await connect(mcp, { bearer: 'bevel_narrow', agentPath: 'Agents/narrow.agent' }, true)).getInstructions();
161
+ const everything = (await connect(mcp, {}, true)).getInstructions();
162
+ expect(narrowed).toBe(
163
+ `${everything}\n\nThis connection is connected as the agent "narrow" (\`Agents/narrow.agent\`): it loads only the ` +
164
+ 'platform tools, connectors, skills and plugins that file lists, and of those only what you may read.',
165
+ );
166
+ expect(everything).not.toContain('connected as the agent');
167
+ });
168
+
169
+ it('two connections of one person made as different agents never share a built surface', async () => {
170
+ const { state, mcp } = await harness();
171
+ vi.spyOn(console, 'log').mockImplementation(() => {});
172
+ await (await connect(mcp, { bearer: 'bevel_narrow', agentPath: 'Agents/narrow.agent' })).listTools();
173
+ await (await connect(mcp, { bearer: 'bevel_narrow', agentPath: 'Agents/other.agent' })).listTools();
174
+ await (await connect(mcp, { bearer: 'bevel_narrow', agentPath: 'Agents/narrow.agent' })).listTools();
175
+ expect(state.builds).toBe(2);
176
+ });
177
+ });
178
+
179
+ describe('a connection whose agent file is gone', () => {
180
+ it('is served an empty listing carrying the reason, and every call — meta-tools too — answers it', async () => {
181
+ const { mcp } = await harness();
182
+ vi.spyOn(console, 'log').mockImplementation(() => {});
183
+ const client = await connect(mcp, { bearer: 'bevel_gone', agentPath: 'Agents/gone.agent' });
184
+ const listed = await client.listTools();
185
+ expect(listed.tools).toEqual([]);
186
+ expect(listed._meta).toEqual({ [CONNECTION_META_KEY]: GONE.unavailable });
187
+ for (const name of ['read_file', 'call_tool_chain']) {
188
+ const res = await client.callTool({ name, arguments: {} });
189
+ expect(res.isError).toBe(true);
190
+ expect(textOf(res)).toBe(GONE.unavailable);
191
+ }
192
+ const instructions = (await connect(mcp, { bearer: 'bevel_gone', agentPath: 'Agents/gone.agent' }, true)).getInstructions();
193
+ expect(instructions?.endsWith(GONE.unavailable!)).toBe(true);
194
+ });
195
+ });
@@ -0,0 +1,192 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { InternalTokenService } from '../../tool-auth/internal-token.service.js';
3
+ import { createTokenVerifier } from '../../tool-auth/tool-auth.middleware.js';
4
+ import type { IExternalApiKeyService } from '../../tool-auth/external-api-key.interface.js';
5
+ import type { BevelOAuthProvider } from '../oauth/bevel-oauth-provider.js';
6
+ import { NOT_AN_AGENT_CHOICE } from '../oauth/oauth-consent.routes.js';
7
+ import { agentFile, agentScopeOver, inMemoryKb } from '../../agent-scope/__tests__/in-memory-kb.js';
8
+ import { closeMountedRoutes, mountMcpRoutes } from './mcp-routes-harness.js';
9
+
10
+ /**
11
+ * Connecting as an agent on the MCP router: the key-mint path takes the same
12
+ * "Connect as" choice the consent step does, the dialog is told the same
13
+ * agents, the local server's exchanged grant carries the connection's agent,
14
+ * and every MCP request hands the proxy the agent its credential connects as.
15
+ */
16
+
17
+ const ADA = 'ada@example.com';
18
+ const kb = inMemoryKb({
19
+ 'Agents/ops.agent': agentFile('name: ops\ndescription: Watches the dashboards.'),
20
+ 'Agents/secret.agent': agentFile('name: secret'),
21
+ });
22
+ kb.hidden.add('Agents/secret.agent');
23
+
24
+ afterEach(async () => {
25
+ await closeMountedRoutes();
26
+ vi.restoreAllMocks();
27
+ });
28
+
29
+ function keyService() {
30
+ const mint = vi.fn(async (_userId: string, label: string, options?: { agentPath?: string }) => ({
31
+ plaintext: 'bevel_new',
32
+ summary: { id: 'k1', label, agentPath: options?.agentPath ?? null },
33
+ }));
34
+ return {
35
+ service: { looksLikeExternalApiKey: (t: string) => t.startsWith('bevel_'), mint } as unknown as IExternalApiKeyService,
36
+ mint,
37
+ };
38
+ }
39
+
40
+ const post = (url: string, body: unknown) =>
41
+ fetch(url, {
42
+ method: 'POST',
43
+ headers: { 'content-type': 'application/json', 'x-test-email': ADA },
44
+ body: JSON.stringify(body),
45
+ });
46
+
47
+ describe('minting a connection key', () => {
48
+ it('as Everything: no choice is minted exactly as before', async () => {
49
+ const { service, mint } = keyService();
50
+ const base = await mountMcpRoutes({ externalApiKeyService: service, agents: agentScopeOver(kb) });
51
+ const res = await post(`${base}/api/mcp/external-api-keys`, { label: 'laptop' });
52
+ expect(res.status).toBe(200);
53
+ expect(mint).toHaveBeenCalledWith('user-A', 'laptop', {});
54
+ });
55
+
56
+ it('as an agent on offer: the key records its normalized path', async () => {
57
+ const { service, mint } = keyService();
58
+ const base = await mountMcpRoutes({ externalApiKeyService: service, agents: agentScopeOver(kb) });
59
+ const res = await post(`${base}/api/mcp/external-api-keys`, { label: 'pipeline', agentPath: 'Agents/ops.agent/' });
60
+ expect(res.status).toBe(200);
61
+ expect(mint).toHaveBeenCalledWith('user-A', 'pipeline', { agentPath: 'Agents/ops.agent' });
62
+ expect(((await res.json()) as { summary: { agentPath: string } }).summary.agentPath).toBe('Agents/ops.agent');
63
+ });
64
+
65
+ it.each([['Agents/secret.agent'], ['Agents/none.agent'], [7]])('refuses %s, minting nothing', async (agentPath) => {
66
+ const { service, mint } = keyService();
67
+ const base = await mountMcpRoutes({ externalApiKeyService: service, agents: agentScopeOver(kb) });
68
+ const res = await post(`${base}/api/mcp/external-api-keys`, { label: 'x', agentPath });
69
+ expect(res.status).toBe(400);
70
+ expect(await res.json()).toEqual({ error: NOT_AN_AGENT_CHOICE });
71
+ expect(mint).not.toHaveBeenCalled();
72
+ });
73
+
74
+ it('offers the same agents the consent step does', async () => {
75
+ const base = await mountMcpRoutes({ agents: agentScopeOver(kb) });
76
+ const res = await fetch(`${base}/api/mcp/agent-choices`, { headers: { 'x-test-email': ADA } });
77
+ expect(await res.json()).toEqual({
78
+ agents: [{ path: 'Agents/ops.agent', name: 'ops', description: 'Watches the dashboards.' }],
79
+ });
80
+ });
81
+
82
+ it('answers a failed listing as a failure, never as "no agents"', async () => {
83
+ const failing = { listChoices: async () => Promise.reject(new Error('clone failed')), choosable: async () => null };
84
+ const base = await mountMcpRoutes({ agents: failing });
85
+ const res = await fetch(`${base}/api/mcp/agent-choices`, { headers: { 'x-test-email': ADA } });
86
+ expect(res.status).toBe(500);
87
+ expect(await res.json()).not.toHaveProperty('agents');
88
+ });
89
+ });
90
+
91
+ describe('the local server’s exchanged grant', () => {
92
+ it("carries the connection's agent, so its calls narrow exactly as the hosted endpoint's", async () => {
93
+ const internalTokens = new InternalTokenService({ secret: 'test-secret' });
94
+ const oauth = {
95
+ looksLikeAccessToken: (t: string) => t.startsWith('bevel-mcp_'),
96
+ verifyAccessToken: async () => ({
97
+ extra: { userId: 'user-5', userEmail: ADA, connectionId: 'conn-9', agentPath: 'Agents/ops.agent' },
98
+ expiresAt: Math.floor(Date.now() / 1000) + 3600,
99
+ }),
100
+ } as unknown as BevelOAuthProvider;
101
+ const base = await mountMcpRoutes({
102
+ externalApiKeyService: { looksLikeExternalApiKey: () => false } as never,
103
+ internalTokens,
104
+ oauthProvider: oauth,
105
+ });
106
+ const res = await fetch(`${base}/api/mcp/local-token`, {
107
+ method: 'POST',
108
+ headers: { Authorization: 'Bearer bevel-mcp_grant' },
109
+ });
110
+ const { token } = (await res.json()) as { token: string };
111
+ const verify = createTokenVerifier({ looksLikeExternalApiKey: () => false } as never, internalTokens);
112
+ expect(await verify(token)).toEqual({
113
+ ok: true,
114
+ auth: { source: 'external', userId: 'user-5', connectionId: 'conn-9', agentPath: 'Agents/ops.agent', scope: 'write' },
115
+ });
116
+ });
117
+
118
+ it('an Everything grant mints the token it always did', async () => {
119
+ const internalTokens = new InternalTokenService({ secret: 'test-secret' });
120
+ const oauth = {
121
+ looksLikeAccessToken: (t: string) => t.startsWith('bevel-mcp_'),
122
+ verifyAccessToken: async () => ({
123
+ extra: { userId: 'user-5', userEmail: ADA, connectionId: 'conn-9', agentPath: null },
124
+ expiresAt: Math.floor(Date.now() / 1000) + 3600,
125
+ }),
126
+ } as unknown as BevelOAuthProvider;
127
+ const base = await mountMcpRoutes({
128
+ externalApiKeyService: { looksLikeExternalApiKey: () => false } as never,
129
+ internalTokens,
130
+ oauthProvider: oauth,
131
+ });
132
+ const res = await fetch(`${base}/api/mcp/local-token`, { method: 'POST', headers: { Authorization: 'Bearer bevel-mcp_g' } });
133
+ const { token } = (await res.json()) as { token: string };
134
+ expect(internalTokens.verify(token)).toEqual({ userId: 'user-5', externalProxy: true, connectionId: 'conn-9' });
135
+ });
136
+
137
+ it("a direct tool call reads the connection's CURRENT choice: a re-consent reaches a token minted before it", async () => {
138
+ const internalTokens = new InternalTokenService({ secret: 'test-secret' });
139
+ const keys = { looksLikeExternalApiKey: () => false } as never;
140
+ let current: { agentPath: string | null } | null = { agentPath: 'Agents/ops.agent' };
141
+ const verify = createTokenVerifier(keys, internalTokens, undefined, {
142
+ liveConnectionChoice: async (id) => (id === 'conn-9' ? current : null),
143
+ });
144
+ // Minted as Everything, served as the agent chosen since.
145
+ const everything = internalTokens.mint({ userId: 'user-5', externalProxy: true, connectionId: 'conn-9' }, 60_000);
146
+ expect(await verify(everything)).toEqual({
147
+ ok: true,
148
+ auth: { source: 'external', userId: 'user-5', connectionId: 'conn-9', agentPath: 'Agents/ops.agent', scope: 'write' },
149
+ });
150
+ // Minted as an agent, served as Everything once the person chose it.
151
+ current = { agentPath: null };
152
+ const asAgent = internalTokens.mint(
153
+ { userId: 'user-5', externalProxy: true, connectionId: 'conn-9', agentPath: 'Agents/ops.agent' },
154
+ 60_000,
155
+ );
156
+ expect(await verify(asAgent)).toEqual({
157
+ ok: true,
158
+ auth: { source: 'external', userId: 'user-5', connectionId: 'conn-9', scope: 'write' },
159
+ });
160
+ });
161
+
162
+ it("refuses a direct tool call through a revoked connection's token, whatever the token says it connects as", async () => {
163
+ const internalTokens = new InternalTokenService({ secret: 'test-secret' });
164
+ const keys = { looksLikeExternalApiKey: () => false } as never;
165
+ const verify = createTokenVerifier(keys, internalTokens, undefined, { liveConnectionChoice: async () => null });
166
+ const token = internalTokens.mint(
167
+ { userId: 'user-5', externalProxy: true, connectionId: 'conn-9', agentPath: 'Agents/ops.agent' },
168
+ 60_000,
169
+ );
170
+ expect(await verify(token)).toEqual({ ok: false, status: 401, message: 'Access for this agent was revoked' });
171
+ });
172
+ });
173
+
174
+ describe('an MCP request', () => {
175
+ it("hands the proxy the agent its credential connects as — and nothing for Everything", async () => {
176
+ const createRequestServer = vi.fn(async () => {
177
+ throw new Error('stop here');
178
+ });
179
+ const base = await mountMcpRoutes({ mcpService: { createRequestServer } });
180
+ const call = (headers: Record<string, string>) =>
181
+ fetch(`${base}/api/mcp`, {
182
+ method: 'POST',
183
+ headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', ...headers },
184
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
185
+ });
186
+ await call({ 'x-test-agent-path': 'Agents/ops.agent' });
187
+ await call({});
188
+ const callers = createRequestServer.mock.calls.map((c) => (c as unknown[])[0] as Record<string, unknown>);
189
+ expect(callers[0]).toMatchObject({ userId: 'user-A', agentPath: 'Agents/ops.agent' });
190
+ expect(callers[1]).not.toHaveProperty('agentPath');
191
+ });
192
+ });