Batch toolkit policy resolution to bound MCP connect time#1157
Merged
Conversation
Toolkit MCP endpoints resolved tool policy once per tool while building the execute-tool description (connections.list walks the whole catalog). That is an N+1 of two uncached storage reads per tool, scaling with total catalog size rather than toolkit size, so a workspace with thousands of tools pushed a toolkit connect past the MCP client's connect timeout. Add an optional prepare() to ToolPolicyProvider: core calls it once per operation to fetch policies and connections in a single pass, then reuses a pure per-tool resolver for the rest of that operation. The toolkit provider implements it; the executor prefers it via a "prepared" rule-set variant and falls back to the existing resolve/list paths. Scope is per-operation (not memoized on the session-scoped provider) so policy state stays fresh. Resolution semantics are unchanged: connectivity gating, per-tool plugin default, and the org personal-dynamic block are all preserved. e2e coverage: - cloud/toolkit-policy-perf.test.ts seeds a production-shaped catalog (one real spec plus synthetic sources, ~2,200 tools across 11 sources) and asserts a toolkit connect adds only a bounded cost over a near-empty control. Pre-fix that delta is ~28s; post-fix it is sub-second. - cloud/toolkit-opencode-real.test.ts drives the real OpenCode binary through OAuth against a large-catalog toolkit and asserts it connects. - scenarios/support/large-catalog.ts is the shared seeding helper. - src/clients/opencode.ts also shims xdg-open so the browser-open hop is captured on Linux, not only macOS.
Greptile SummaryThis PR batches toolkit policy lookup during MCP operations. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[tools/list or tools/call] --> B[listActivePolicyRuleSet]
B --> C{policy provider present}
C -->|no| D[load global policy rows]
C -->|prepare exists| E[prepare once per operation]
C -->|resolve only| F[resolve per tool]
C -->|list only| G[list rules once]
E --> H[pure per-tool resolver]
F --> I[effective policy]
G --> I
D --> I
H --> I
%%{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[tools/list or tools/call] --> B[listActivePolicyRuleSet]
B --> C{policy provider present}
C -->|no| D[load global policy rows]
C -->|prepare exists| E[prepare once per operation]
C -->|resolve only| F[resolve per tool]
C -->|list only| G[list rules once]
E --> H[pure per-tool resolver]
F --> I[effective policy]
G --> I
D --> I
H --> I
Reviews (1): Last reviewed commit: "Batch toolkit policy resolution to bound..." | Re-trigger Greptile |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 60b6cc1 | Commit Preview URL Branch Preview URL |
Jun 27 2026, 06:24 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 60b6cc1 | Jun 27 2026, 06:25 AM |
Contributor
Cloudflare previewTorn down — the PR is closed. |
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Toolkit MCP endpoints resolved tool policy once per tool over the whole catalog while building the execute-tool description. That N+1 scaled with total catalog size, so large workspaces pushed a toolkit connect past the MCP client connect timeout.
prepare()lets the provider fetch policies and connections once per operation and reuse a pure per-tool resolver; the executor prefers it and falls back to the existing paths. Resolution semantics are unchanged.Real OpenCode connecting to a toolkit over an 11-source, ~3.3k-tool catalog (post-fix):
Tests:
e2e/cloud/toolkit-policy-perf.test.tsbounds the connect cost (pre-fix ~28s of extra connect time, post-fix sub-second), ande2e/cloud/toolkit-opencode-real.test.tsdrives the real OpenCode binary through OAuth.