Scope integrations.list to the active toolkit policy#1241
Conversation
A toolkit-scoped MCP session listed the full workspace provider catalog through integrations.list, including providers the toolkit grants no tools from. tools.list and connections.list were already narrowed to the toolkit's granted surface, but integrations.list was not, so a provider with zero granted tools still surfaced in the catalog (via the integrations.list core tool and the console "Tool providers" view). Filter integrationsList by the active tool policy provider, mirroring connectionsList: static system sources stay, and DB-backed providers are kept only when they contribute at least one visible tool under the policy. Behavior is unchanged when no policy provider is active (the workspace console still lists the full catalog). Covered by an e2e scenario that drives the real toolkit MCP endpoint and asserts a provider the toolkit grants no tools is absent from the catalog while still callable providers remain.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | e8ed3f0 | Jul 01 2026, 08:08 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | e8ed3f0 | Commit Preview URL Branch Preview URL |
Jul 01 2026, 08:07 PM |
Cloudflare previewTorn down — the PR is closed. |
Greptile SummaryThis PR scopes
Confidence Score: 4/5Safe to merge. The core change is a small, focused addition that correctly mirrors an already-proven pattern, and the e2e scenario validates both the positive and negative cases end-to-end. The The finalizer in Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[integrationsList called] --> B{activeToolPolicyProvider set?}
B -- No --> C[Return full catalog:\nstaticIntegrations + dbIntegrations]
B -- Yes --> D[Call toolsList with includeAnnotations=false]
D --> E[Filter: non-static tools only]
E --> F[Build Set of visibleIntegrationSlugs]
F --> G[Filter dbIntegrations\nby slug in visibleIntegrationSlugs]
G --> H[Return staticIntegrations + filtered dbIntegrations]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[integrationsList called] --> B{activeToolPolicyProvider set?}
B -- No --> C[Return full catalog:\nstaticIntegrations + dbIntegrations]
B -- Yes --> D[Call toolsList with includeAnnotations=false]
D --> E[Filter: non-static tools only]
E --> F[Build Set of visibleIntegrationSlugs]
F --> G[Filter dbIntegrations\nby slug in visibleIntegrationSlugs]
G --> H[Return staticIntegrations + filtered dbIntegrations]
Reviews (1): Last reviewed commit: "Scope integrations.list to the active to..." | Re-trigger Greptile |
| Effect.ensuring( | ||
| Effect.gen(function* () { | ||
| const listed = yield* client.toolkits.list(); | ||
| yield* Effect.forEach( | ||
| listed.toolkits.filter((toolkit) => toolkit.name === toolkitName), | ||
| (toolkit) => client.toolkits.remove({ params: { toolkitId: toolkit.id } }), | ||
| { discard: true }, | ||
| ); | ||
| }).pipe(Effect.ignore), | ||
| ), |
There was a problem hiding this comment.
Incomplete cleanup in
Effect.ensuring
The finalizer removes the toolkit but leaves the two created integrations (granted, ungranted) and their connections behind. Per e2e/AGENTS.md, all resources created in a scenario must be torn down by Effect.ensuring — a mid-test failure must not leak into the shared selfhost instance. On selfhost every scenario shares the bootstrap admin, so these orphaned integrations accumulate across runs. The unique() prefix prevents collision on slug counts, but the leaked rows still grow the global catalog over time.
Context Used: e2e/AGENTS.md (source)
Problem
A toolkit-scoped MCP session could still see the full workspace provider catalog through
integrations.list, including providers the toolkit grants no tools from.tools.list,tools.search, andconnections.listwere already narrowed to the toolkit's granted surface, butintegrations.listwas not.The result: a provider the toolkit blocks entirely (zero granted tools) still showed up in the catalog as a "provider with 0 tools" — both through the
integrations.listcore tool an agent can call over MCP and through the console "Tool providers" view. That is a small capability leak (the scoped agent learns which providers exist elsewhere) and noise.Fix
Scope
integrationsListby the active tool policy provider, mirroring the existingconnectionsListbehavior:Both
executor.integrations.listand theintegrations.listcore tool route through this one function, so the MCP surface and the console are fixed together.Testing
Added an e2e scenario (
scenarios/toolkits-mcp.test.ts) that drives the real toolkit MCP endpoint:executor.coreTools.*introspection namespace), not the other.integrations.listlists the granted provider and excludes the ungranted one, which also exposes no callable tools.Verified both ways: with the fix reverted the scenario fails (the ungranted provider appears in the toolkit catalog); with the fix it passes. All toolkit MCP scenarios pass on selfhost.