@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.
- package/agent-guide/access-control.md +146 -289
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +239 -8
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +133 -155
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +15 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +44 -16
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +39 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +15 -3
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +239 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +58 -33
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +7 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +74 -48
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +25 -13
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +531 -137
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +14 -1
- package/src/core/create-core-services.ts +62 -15
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +43 -10
- package/src/modules/access/access-control.service.ts +250 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +66 -37
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/index.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
- package/src/modules/agent-instructions/compose.ts +7 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +112 -52
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +45 -14
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +574 -176
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/path-order.ts +37 -8
- package/src/shared/read-capped.ts +40 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- 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
|
-
/**
|
|
57
|
-
live?: (connectionId: string) => Promise<
|
|
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
|
-
|
|
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).
|
|
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 () =>
|
|
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
|
+
});
|