@bevel-software/platform-core-backend 0.33.0 → 0.34.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 +19 -8
- package/agent-guide/html-views.md +231 -5
- package/agent-guide/tool-manuals.md +1 -1
- 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 +14 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +16 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +37 -12
- 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 +17 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +12 -2
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +164 -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 +35 -29
- 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/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.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
- package/dist/modules/agent-guide/agent-guide.tools.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 +3 -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 +16 -1
- 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/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 +68 -13
- 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/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 +15 -3
- package/dist/modules/secrets-vault/secrets-vault.routes.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 +24 -12
- 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-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/tool-manuals.contract.d.ts +19 -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 +13 -6
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
- 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 +14 -9
- 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/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.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +460 -116
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- 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/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 +3 -3
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/slow-ripgrep.ts +65 -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/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +13 -0
- package/src/core/create-core-services.ts +49 -12
- 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 +2 -0
- 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__/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 +17 -10
- package/src/modules/access/access-control.service.ts +158 -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 +43 -33
- 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/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 +31 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
- package/src/modules/agent-instructions/compose.ts +3 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
- 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/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 +107 -17
- 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/secrets-vault.routes.ts +23 -3
- 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 +44 -13
- 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__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- 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.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
- package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
- 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 +14 -9
- 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 +2 -2
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
- 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 +500 -155
- package/src/shared/path-order.ts +37 -8
- 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/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,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
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import type { Server } from 'node:http';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { createOAuthConsentRoutes, NOT_AN_AGENT_CHOICE } from '../oauth/oauth-consent.routes.js';
|
|
6
|
+
import { signAuthRequest } from '../oauth/oauth-state.js';
|
|
7
|
+
import { agentFile, agentScopeOver, inMemoryKb } from '../../agent-scope/__tests__/in-memory-kb.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The consent step's "Connect as" choice, through the two routes the
|
|
11
|
+
* /connect page calls: the request description offers the agents the person
|
|
12
|
+
* may read, and Finish hands the provider exactly the choice made — null for
|
|
13
|
+
* Everything — or refuses one that is not on offer, before any code is minted.
|
|
14
|
+
* The choice offered and checked is the REAL agent-scope service's, over a
|
|
15
|
+
* knowledge base held in memory.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const SECRET = 'state-secret';
|
|
19
|
+
const ADA = 'ada@example.com';
|
|
20
|
+
|
|
21
|
+
let http: Server | null = null;
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
if (http) await new Promise<void>((r) => http!.close(() => r()));
|
|
24
|
+
http = null;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const kb = inMemoryKb({
|
|
28
|
+
'Agents/mechanic.agent': agentFile('name: delivery-mechanic\ndescription: Moves code around.'),
|
|
29
|
+
'Agents/secret.agent': agentFile('name: secret'),
|
|
30
|
+
});
|
|
31
|
+
kb.hidden.add('Agents/secret.agent');
|
|
32
|
+
|
|
33
|
+
async function mount(opts: { email?: string; agents?: Parameters<typeof createOAuthConsentRoutes>[0]['agents'] } = {}) {
|
|
34
|
+
const issueAuthCode = vi.fn(async () => ({ redirectTo: 'https://agent.example.com/cb?code=c1' }));
|
|
35
|
+
const completeConsent = vi.fn(async () => ({ redirectTo: 'https://claude.ai/cb?code=gh' }));
|
|
36
|
+
const app = express();
|
|
37
|
+
app.use(express.json());
|
|
38
|
+
app.use((req, _res, next) => {
|
|
39
|
+
req.userId = 'u1';
|
|
40
|
+
req.userEmail = opts.email ?? ADA;
|
|
41
|
+
next();
|
|
42
|
+
});
|
|
43
|
+
app.use(
|
|
44
|
+
createOAuthConsentRoutes({
|
|
45
|
+
provider: {
|
|
46
|
+
issueAuthCode,
|
|
47
|
+
clientsStore: { getClient: async () => ({ client_name: 'Claude' }) },
|
|
48
|
+
} as never,
|
|
49
|
+
stateSecret: SECRET,
|
|
50
|
+
facade: {
|
|
51
|
+
isFacadeRequest: () => true,
|
|
52
|
+
clientNameFor: () => 'Claude',
|
|
53
|
+
completeConsent,
|
|
54
|
+
},
|
|
55
|
+
agents: opts.agents ?? agentScopeOver(kb),
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
http = app.listen(0);
|
|
59
|
+
await new Promise<void>((r) => http!.once('listening', r));
|
|
60
|
+
const base = `http://127.0.0.1:${(http.address() as AddressInfo).port}`;
|
|
61
|
+
return { base, issueAuthCode, completeConsent };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const state = (extra: Record<string, unknown> = {}) =>
|
|
65
|
+
signAuthRequest(SECRET, { c: 'client-1', r: 'https://agent.example.com/cb', cc: 'challenge', ...extra });
|
|
66
|
+
|
|
67
|
+
async function finish(base: string, body: Record<string, unknown>) {
|
|
68
|
+
return fetch(`${base}/mcp/oauth/complete`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'content-type': 'application/json' },
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('the consent step offers "Connect as"', () => {
|
|
76
|
+
it('lists the agents the person may read, by name with description and path — Everything is the page’s own', async () => {
|
|
77
|
+
const { base } = await mount();
|
|
78
|
+
const res = await fetch(`${base}/mcp/oauth/request?state=${encodeURIComponent(state())}`);
|
|
79
|
+
expect(await res.json()).toEqual({
|
|
80
|
+
clientName: 'Claude',
|
|
81
|
+
scope: null,
|
|
82
|
+
resource: null,
|
|
83
|
+
agents: [{ path: 'Agents/mechanic.agent', name: 'delivery-mechanic', description: 'Moves code around.' }],
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('says so when the agents cannot be listed, rather than passing it off as having none', async () => {
|
|
88
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
89
|
+
const failing = {
|
|
90
|
+
listChoices: async () => {
|
|
91
|
+
throw new Error('checkout unavailable');
|
|
92
|
+
},
|
|
93
|
+
choosable: async () => null,
|
|
94
|
+
};
|
|
95
|
+
const { base } = await mount({ agents: failing });
|
|
96
|
+
const res = await fetch(`${base}/mcp/oauth/request?state=${encodeURIComponent(state())}`);
|
|
97
|
+
expect(await res.json()).toEqual({
|
|
98
|
+
clientName: 'Claude',
|
|
99
|
+
scope: null,
|
|
100
|
+
resource: null,
|
|
101
|
+
agents: [],
|
|
102
|
+
agentsUnavailable: true,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Finish records the choice', () => {
|
|
108
|
+
it('Everything — no choice, null or empty — reaches the provider as null, exactly as before', async () => {
|
|
109
|
+
const { base, issueAuthCode } = await mount();
|
|
110
|
+
for (const body of [{}, { agentPath: null }, { agentPath: '' }]) {
|
|
111
|
+
const st = state();
|
|
112
|
+
const res = await finish(base, { state: st, ...body });
|
|
113
|
+
expect(res.status).toBe(200);
|
|
114
|
+
expect(await res.json()).toEqual({ redirectTo: 'https://agent.example.com/cb?code=c1' });
|
|
115
|
+
}
|
|
116
|
+
expect(issueAuthCode).toHaveBeenCalledTimes(3);
|
|
117
|
+
for (const call of issueAuthCode.mock.calls as unknown as unknown[][]) expect(call[2]).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('an agent on offer reaches the provider as its normalized path', async () => {
|
|
121
|
+
const { base, issueAuthCode } = await mount();
|
|
122
|
+
const res = await finish(base, { state: state(), agentPath: '/Agents/mechanic.agent' });
|
|
123
|
+
expect(res.status).toBe(200);
|
|
124
|
+
expect(issueAuthCode).toHaveBeenCalledWith('u1', expect.objectContaining({ c: 'client-1' }), 'Agents/mechanic.agent');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it.each([
|
|
128
|
+
['a file the person may not read', 'Agents/secret.agent'],
|
|
129
|
+
['a file that does not exist', 'Agents/none.agent'],
|
|
130
|
+
['a path that is not an agent', 'notes.md'],
|
|
131
|
+
['a value that is not a path', 42],
|
|
132
|
+
])('refuses %s with one answer, minting nothing', async (_what, agentPath) => {
|
|
133
|
+
const { base, issueAuthCode } = await mount();
|
|
134
|
+
const res = await finish(base, { state: state(), agentPath });
|
|
135
|
+
expect(res.status).toBe(400);
|
|
136
|
+
expect(await res.json()).toEqual({ error: NOT_AN_AGENT_CHOICE });
|
|
137
|
+
expect(issueAuthCode).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('refuses a choice on an account linked through the GitHub facade, which has no connection to record it on', async () => {
|
|
141
|
+
const { base, completeConsent } = await mount();
|
|
142
|
+
const res = await finish(base, { state: state({ gh: true, cc: undefined }), agentPath: 'Agents/mechanic.agent' });
|
|
143
|
+
expect(res.status).toBe(400);
|
|
144
|
+
expect(completeConsent).not.toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -24,6 +24,13 @@ declare global {
|
|
|
24
24
|
* the page its call comes from (`X-Bevel-Page`).
|
|
25
25
|
*/
|
|
26
26
|
mcpCallerKind?: McpCallerKind;
|
|
27
|
+
/**
|
|
28
|
+
* The agent configuration the MCP caller's credential connects as — a key's
|
|
29
|
+
* `api_tokens.agent_path`, an agent connection's
|
|
30
|
+
* `agent_connections.agent_path`, or the one an exchanged grant
|
|
31
|
+
* carries. Unset is Everything.
|
|
32
|
+
*/
|
|
33
|
+
mcpAgentPath?: string;
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -110,6 +117,7 @@ export function createMcpAuthMiddleware(
|
|
|
110
117
|
req.userId = resolved.user.id;
|
|
111
118
|
req.userEmail = resolved.user.email;
|
|
112
119
|
req.externalApiKeyId = resolved.tokenId;
|
|
120
|
+
if (resolved.agentPath) req.mcpAgentPath = resolved.agentPath;
|
|
113
121
|
req.mcpCallerKind = 'key';
|
|
114
122
|
next();
|
|
115
123
|
return;
|
|
@@ -137,6 +145,9 @@ export function createMcpAuthMiddleware(
|
|
|
137
145
|
if (typeof info.extra?.connectionId === 'string') {
|
|
138
146
|
req.agentConnectionId = info.extra.connectionId;
|
|
139
147
|
}
|
|
148
|
+
if (typeof info.extra?.agentPath === 'string' && info.extra.agentPath) {
|
|
149
|
+
req.mcpAgentPath = info.extra.agentPath;
|
|
150
|
+
}
|
|
140
151
|
if (!req.userId) {
|
|
141
152
|
unauthorized(res, 'Invalid access token');
|
|
142
153
|
return;
|
|
@@ -217,10 +228,13 @@ export function createMcpAuthMiddleware(
|
|
|
217
228
|
// stamped too (throttled, off the request's path), so the Audit log's
|
|
218
229
|
// "last used" follows the local server's calls, not only its sign-ins.
|
|
219
230
|
// Carried on the request so those calls are logged under the agent.
|
|
231
|
+
// The same read answers what the connection is made as NOW: a
|
|
232
|
+
// re-consent changes the row, not the token, so the row's choice is
|
|
233
|
+
// the one served, never the one the token was minted with.
|
|
220
234
|
if (claim.connectionId) {
|
|
221
|
-
let live:
|
|
235
|
+
let live: { agentPath: string | null } | null;
|
|
222
236
|
try {
|
|
223
|
-
live = await oauthProvider.
|
|
237
|
+
live = await oauthProvider.liveConnectionChoice(claim.connectionId);
|
|
224
238
|
} catch (err) {
|
|
225
239
|
log.error('agent-connection check failed:', { err });
|
|
226
240
|
res.status(500).json({ error: 'Authentication backend unavailable' });
|
|
@@ -232,6 +246,9 @@ export function createMcpAuthMiddleware(
|
|
|
232
246
|
}
|
|
233
247
|
oauthProvider.noteConnectionUse(claim.connectionId);
|
|
234
248
|
req.agentConnectionId = claim.connectionId;
|
|
249
|
+
if (live.agentPath) req.mcpAgentPath = live.agentPath;
|
|
250
|
+
} else if (claim.agentPath) {
|
|
251
|
+
req.mcpAgentPath = claim.agentPath;
|
|
235
252
|
}
|
|
236
253
|
req.mcpCallerKind = 'internal';
|
|
237
254
|
next();
|