Skip to content

fix(agent): scope hosted child MCP tools to the active project - #3624

Merged
kwakayama merged 11 commits into
mainfrom
fix/project-scoped-child-mcp
Aug 12, 2026
Merged

fix(agent): scope hosted child MCP tools to the active project#3624
kwakayama merged 11 commits into
mainfrom
fix/project-scoped-child-mcp

Conversation

@kwakayama

@kwakayama kwakayama commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Hosted invoke_agent child runs discovered first-party tools through the flat /mcp endpoint. That route has no active-project context, so project integrations such as Salesforce were absent even though they were available to the project.

Solution

  • Resolve the Veryfront API MCP URL at request time as /projects/{active-project-id}/mcp, so root, child-fork, and direct runtime paths follow project switches.
  • Keep deployment-owned transport restricted to configured first-party MCP origins and either the exact MCP route or a single project-scoped MCP route.
  • Preserve intentional endpoint query parameters while making the trust decision only from the parsed scheme, origin, and path; redirects remain disabled for authenticated requests.
  • Suppress run IDs for both flat and project-scoped control-plane routes when run-bound authorization is disabled.
  • Preserve the existing remote-MCP validation error for malformed configured URLs.

Validation

  • Focused Deno suite: 115 passed, 0 failed across MCP configuration, hosted root/child sources, direct runtime sources, cloud agent service, and remote transport.
  • Focused formatting, lint, typecheck, and diff checks pass.
  • API reference was regenerated with the repository CI Deno version (2.7.7).

The result restores project-scoped integration discovery for hosted child agents without granting the deployment transport to arbitrary dynamic endpoints.

Summary by CodeRabbit

  • New Features

    • MCP connections now automatically use the active project’s scoped endpoint.
    • Endpoints update seamlessly when switching projects, including during hosted agent sessions.
    • Trusted project-scoped connections now support improved transport handling while preserving secure handling for other endpoints.
  • Bug Fixes

    • Improved endpoint URL handling for project identifiers, existing paths, queries, and invalid URLs.
  • Documentation

    • Updated API reference links and clarified trusted MCP endpoint configuration.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66c0e466-c289-4b29-8491-c3f9512a71ec

📥 Commits

Reviewing files that changed from the base of the PR and between f9ea916 and 1353c55.

📒 Files selected for processing (7)
  • docs/api-reference/veryfront/tool.md
  • src/agent/hosted/project-remote-tool-source.test.ts
  • src/agent/hosted/veryfront-cloud-agent-service.test.ts
  • src/agent/service/mcp-server-config.test.ts
  • src/agent/service/mcp-server-config.ts
  • src/tool/remote-mcp.test.ts
  • src/tool/remote-mcp.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/agent/hosted/project-remote-tool-source.test.ts
  • docs/api-reference/veryfront/tool.md
  • src/agent/service/mcp-server-config.test.ts
  • src/tool/remote-mcp.ts

📝 Walkthrough

Walkthrough

Veryfront API MCP endpoints now include the active project path. Endpoint resolution changes when the project changes. Remote MCP transport selection supports trusted project-scoped routes and guarded unmatched endpoints. Hosted and runtime tests validate the behavior.

Changes

Project-scoped MCP configuration

Layer / File(s) Summary
Project-scoped URL construction
src/agent/service/mcp-server-config.ts, src/agent/service/mcp-server-config.test.ts
Adds createProjectScopedMcpUrl and resolves Veryfront API MCP endpoints from the current project ID. Tests cover encoding, path replacement, blank IDs, invalid URLs, and query strings.
Trusted endpoint transport selection
src/tool/remote-mcp.ts, src/tool/remote-mcp.test.ts, docs/api-reference/veryfront/tool.md
Transport selection evaluates each resolved endpoint. Trusted /mcp and project-scoped routes use the supplied transport. Other routes use guarded transport. Tests cover query handling, path validation, dynamic projects, and MCP metadata.
Hosted and runtime integration
src/agent/hosted/*, src/agent/runtime/mcp-server-tool-sources.test.ts, docs/api-reference/veryfront/agent.md
Hosted and runtime sources resolve dynamic endpoints and validate project changes. Hosted agent tests exercise project switching. API reference source links are updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HostedAgent
  participant RemoteMCPToolSource
  participant ProjectResolver
  participant FetchProvider
  HostedAgent->>RemoteMCPToolSource: request MCP tools
  RemoteMCPToolSource->>ProjectResolver: resolve active project
  ProjectResolver-->>RemoteMCPToolSource: return project-scoped endpoint
  RemoteMCPToolSource->>FetchProvider: select transport for endpoint
  FetchProvider-->>RemoteMCPToolSource: return supplied or guarded fetch
Loading

Possibly related PRs

Suggested reviewers: kojiwakayama, copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: scoping hosted child MCP tools to the active project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/project-scoped-child-mcp

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7831181608

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agent/hosted/project-remote-tool-source.ts Outdated
Comment thread src/agent/hosted/project-remote-tool-source.ts Outdated
@kwakayama kwakayama added the needs-human-input Maintainer action required label Aug 12, 2026
@kwakayama

Copy link
Copy Markdown
Contributor Author

Critical review — 32/100 (do not merge as-is)

The diagnosis is right: integration tools are only discoverable on the project-scoped MCP route, and the flat /mcp route is the reason local child agents see zero of them. The remedy chosen, however, is applied at the wrong layer, at the wrong time, and was not run against the existing suite. Six tests fail because of this change.


Blocking

B1 — CI is red, and the PR body says it is green.

The body claims "fmt/lint/diff checks pass" and that the focused Deno test is "blocked by known local native-brand/typecheck failures." Neither holds. The focused test passes locally (11 passed). What actually fails, reproduced locally on 783118160:

src/agent/hosted/project-remote-tool-source.test.ts .......... 5 failed
src/agent/hosted/veryfront-cloud-agent-service.test.ts ....... 1 failed
FAILED | 66 passed | 6 failed

coverage shard 4/8, coverage shard 8/8, tests (unit) and coverage gate are all red on these. Only one expectation in child-fork-tool-sources.test.ts was updated; the other two suites assert the same endpoint and were never run.

The failing case in veryfront-cloud-agent-service.test.ts is named "root and child runtimes use the deployment-owned remote MCP factory." That is not a stale string — it guards the transport invariant in B3.

B2 — The endpoint is captured once at construction; the project is mutable. (project-remote-tool-source.ts:476)

Every neighbouring parameter is a deferred thunk on purpose:

defaultProjectId: () => activeProjectId(input.taskContext),
getProjectId:     input.getProjectId ?? (() => activeProjectId(input.taskContext)),
getActiveBranchId: ...

chat-runtime-tool-assembly.ts:326-328

This PR calls input.getProjectId() eagerly and freezes the result into a string. The source is not rebuilt on a project switch — createHostedProjectRemoteToolSource handles switching in place via onProjectSwitch and the project-navigation tool path. After switch_project, discovery and execution keep hitting /projects/<old>/mcp while createRunResilientRemoteToolSource caches the result under context.projectId = the new project. Tools listed for one project, cached under another.

Before this PR the flat route was project-agnostic and correct under switching. This makes switching worse, not better.

RemoteMCPToolSourceConfig["endpoint"] is already ResolvableValue<string> (remote-mcp.ts:54) and is resolved per call in both listTools (:855) and executeTool (:922). The fix is a resolver, not a string.

B3 — The run-id authorization classifier was not updated. (remote-mcp.ts:813-821)

function endpointBindsToolAuthorization(endpoint: string): boolean {
  ...
  const controlPlaneEndpoint = normalizeTrustedEndpoint(`${apiBaseUrl.replace(/\/+$/, "")}/mcp`);
  return normalizedEndpoint !== undefined && normalizedEndpoint === controlPlaneEndpoint;
}

Exact match against <api-base>/mcp. <api-base>/projects/<id>/mcp misses it, so suppressRunId silently becomes false for every API MCP call. The suppression exists precisely for runs where the run id must not be read as an authorization binding — project-run-execute.handler.ts:618 sets runIdBindsToolAuthorization: false for local eval runs. After this PR those runs send _meta.run_id into the control-plane integration authorization gate, where it is honoured as a binding for a run the control plane does not own. A load-bearing guard becomes inert with no test noticing.

The same exact-match assumption governs the deployment-owned transport allowlist (createRemoteMCPToolSourceFactoryWithTransport, remote-mcp.ts:1014-1020), which only matches static endpoint strings. Note the interaction with B2's fix: switching to a resolver endpoint also drops off that fast path (typeof config.endpoint === "string" fails). Both call sites need to be reasoned about together, not patched one at a time.


Should fix before merge

S1 — Dispatch duplicated at the call sites instead of at the single point that already dispatches.

The identical server.kind === "veryfront-api" ? … : … ternary is now pasted into two callers. createAgentServiceRemoteMcpConfig already branches on server.kind and already accepts getProjectId in its input type (mcp-server-config.ts:39). Putting the resolver inside createVeryfrontApiRemoteMcpConfig is one edit, cannot be forgotten by the next caller, and fixes S2 for free.

S2 — The third call site was left behind. (mcp-server-tool-sources.ts:271-277)

apiMcpUrl: `${bootstrap.apiBaseUrl.replace(/\/+$/, "")}/mcp`,
getProjectId: () => projectId,

projectId is not merely available here, it is required (:261 throws without it). Under this PR's own premise — flat /mcp lists zero integration tools — this path has exactly the bug being fixed. bindRemoteToolSourceToProject binds project into execution args and context, but listing goes to the URL. Either fix it or say in the description why the project-scoped route does not apply to direct application routes.

S3 — createProjectScopedMcpUrl has no direct test and three edge-case defects.

Probed against the real implementation:

input projectId output
https://api.example/mcp project-1 …/projects/project-1/mcp
https://api.example project-1 https://api.example**//**projects/project-1/mcp ❌ double slash
https://api.example/projects/old/mcp new …/projects/old/**projects/new**/mcp ❌ not idempotent
https://api.example/mcp " " …/projects/**%20%20**/mcp ❌ whitespace is truthy

The whitespace case is inconsistent with the repo's own convention — bootstrap.projectSlug?.trim() two files over. VERYFRONT_MCP_URL is env-supplied (cloud-agent-chat-execution.ts:158), so the bare-base-URL case is reachable by misconfiguration, and a // path is a silent 404 rather than a clear error. Zero of these are covered; the PR adds no test for this function at all, only edits one expected string.

S4 — ci (lint) failure. Stale docs/api-reference/veryfront/agent.md — the new import block shifted line anchors in child-fork-tool-sources.ts. deno task docs regenerates it cleanly (6 lines). AGENTS.md: "When a public schema changes, update the corresponding generated docs, examples, and tests." Mechanical, but it is a required gate.

S5 — New exported symbol, no doc comment. Every other export in both touched files carries one (/** Input payload for… */, /** Public API contract for… */). createProjectScopedMcpUrl is exported bare, and is exported across a module boundary into child-fork-tool-sources.ts — so it is API surface, not a local helper.


What holds up

  • The root cause is correctly identified and the staging evidence (0 tools at /mcp, 5 at /projects/{project}/mcp) is the right thing to have measured.
  • Scope is small, focused, and trivially revertable.
  • Non-API server kinds are correctly left untouched, and the null-project fallback is the right default.
  • deno lint and ci (typecheck) are clean.

Suggested shape

Move the whole thing into createVeryfrontApiRemoteMcpConfig as a resolver, and update the endpoint classifier in the same PR:

// mcp-server-config.ts — one place, deferred, covers all three call sites
return {
  id: server.id ?? input.defaultSourceId ?? "veryfront-mcp",
  endpoint: () => createProjectScopedMcpUrl(input.apiMcpUrl, input.getProjectId?.()),
  headers: (context) => { /* unchanged */ },
};

Then: trim + guard the project id, make the helper idempotent, normalise trailing slashes, teach endpointBindsToolAuthorization (and the trusted-endpoint allowlist) about the project-scoped route, add direct unit tests for the helper including a project-switch case, update the three failing suites, and run deno task docs.

32/100. Right problem, right evidence, wrong layer — and shipped without running the suites that assert the invariant it changes. The remedy is roughly a 15-line rewrite; the score reflects the current diff, not the difficulty of fixing it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/agent/hosted/project-remote-tool-source.test.ts`:
- Around line 92-107: Add regression tests in createProjectScopedMcpUrl for an
already project-scoped URL and a whitespace-only project ID, then update
createProjectScopedMcpUrl to preserve already scoped URLs and ignore or reject
blank IDs. Keep existing unscoped and null-project behavior unchanged, and
implement the tests before the helper change.

In `@src/agent/hosted/veryfront-cloud-agent-service.test.ts`:
- Line 149: Extend the tests around createAgentRuntime and onStudioProjectSwitch
to switch from project-1 to project-2, then list or execute a remote tool.
Assert that both the root runtime and its child-fork configuration resolve the
project-2 MCP endpoint, adding this focused regression before changing
implementation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a87fdebd-7607-491a-9f3a-c596a79cef96

📥 Commits

Reviewing files that changed from the base of the PR and between 7831181 and f7af726.

📒 Files selected for processing (3)
  • docs/api-reference/veryfront/agent.md
  • src/agent/hosted/project-remote-tool-source.test.ts
  • src/agent/hosted/veryfront-cloud-agent-service.test.ts

Comment thread src/agent/hosted/project-remote-tool-source.test.ts Outdated
Comment thread src/agent/hosted/veryfront-cloud-agent-service.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/api-reference/veryfront/tool.md`:
- Around line 130-131: Regenerate the API reference links for
createRemoteMCPToolSource and createRemoteMCPToolSourceFactoryWithTransport,
updating them to the current declaration locations (984 and 1015) and applying
the same correction to the additionally referenced entry at line 173.

In `@src/agent/service/mcp-server-config.test.ts`:
- Around line 59-79: Replace the Deno.test-based cases with describe() and it()
from `#veryfront/testing/bdd.ts`: convert the createProjectScopedMcpUrl test in
src/agent/service/mcp-server-config.test.ts (lines 59-79) and the API-only MCP
test in src/agent/hosted/project-remote-tool-source.test.ts (lines 908-935).
Preserve each test’s existing assertions and behavior.

In `@src/tool/remote-mcp.ts`:
- Around line 817-831: Update normalizeTrustedEndpoint and the related
trusted-endpoint handling around createProjectScopedMcpUrl to validate only the
parsed URL origin and pathname, while allowing and preserving query parameters
in the returned request target. Ensure query-bearing project-scoped
/projects/{id}/mcp endpoints remain trusted, suppress non-binding runId
behavior, and use requestFetch; add focused authorization and transport tests
covering this endpoint shape.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3781a35e-f92e-4840-a15d-8b107d8539b6

📥 Commits

Reviewing files that changed from the base of the PR and between f7af726 and 8613be5.

📒 Files selected for processing (11)
  • docs/api-reference/veryfront/agent.md
  • docs/api-reference/veryfront/tool.md
  • src/agent/hosted/child-fork-tool-sources.test.ts
  • src/agent/hosted/child-fork-tool-sources.ts
  • src/agent/hosted/project-remote-tool-source.test.ts
  • src/agent/hosted/veryfront-cloud-agent-service.test.ts
  • src/agent/runtime/mcp-server-tool-sources.test.ts
  • src/agent/service/mcp-server-config.test.ts
  • src/agent/service/mcp-server-config.ts
  • src/tool/remote-mcp.test.ts
  • src/tool/remote-mcp.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/api-reference/veryfront/agent.md
  • src/agent/hosted/veryfront-cloud-agent-service.test.ts

Comment thread docs/api-reference/veryfront/tool.md Outdated
Comment thread src/agent/service/mcp-server-config.test.ts Outdated
Comment thread src/tool/remote-mcp.ts Outdated
@kwakayama

Copy link
Copy Markdown
Contributor Author

Addressed the review findings in the latest commits:

  • project-scoped MCP endpoints resolve per request and update after project switches; root, child, and direct-runtime paths share the central config;
  • project-scoped control-plane routes retain deployment transport and suppress non-binding run IDs, including query-bearing URLs;
  • helper normalization, portable BDD coverage, and generated API references are updated.

Local evidence: 115 focused tests pass; API reference generation matches Linux/Deno 2.7.7 byte-for-byte.

@kwakayama
kwakayama added this pull request to the merge queue Aug 12, 2026
@kwakayama
kwakayama removed this pull request from the merge queue due to a manual request Aug 12, 2026
@kwakayama

Copy link
Copy Markdown
Contributor Author

Re-review at 817a794 — 88/100 (was 32/100)

Every blocking item from the previous round is fixed, and fixed at the right layer rather than papered over. The endpoint resolver landed in createVeryfrontApiRemoteMcpConfig, which collapsed three call sites into one and picked up mcp-server-tool-sources.ts for free. CI is fully green; 115 tests pass locally across the six affected suites.

The bulk of what remains is one deliberate guard removal that isn't called out, and a PR description that no longer describes the PR.


Verified fixed

B1 — CI red / body claimed green. All 27 checks pass. Locally: 115 passed | 0 failed across project-remote-tool-source, veryfront-cloud-agent-service, child-fork-tool-sources, mcp-server-config, mcp-server-tool-sources, remote-mcp.

B2 — Endpoint frozen at construction. Now a resolver:

endpoint: () => createProjectScopedMcpUrl(input.apiMcpUrl, input.getProjectId?.()),

and project-remote-tool-source.ts is reverted to zero diff, which is the right outcome — it was already passing getProjectId through. The regression test is the real thing, not a string swap: child-fork-tool-sources.test.ts now mutates activeProjectId inside onConfirmedStudioProjectSwitch and asserts the endpoint follows to /projects/project-2/mcp after the switch. That is exactly the failure mode I described.

B3 — Run-id authorization classifier. endpointBindsToolAuthorization now matches both <base>/mcp and <base>/projects/<id>/mcp, origin-checked. Covered by a test asserting _meta is { agent_id } with no run_id for a project-scoped endpoint under runIdBindsToolAuthorization: false.

S1 — Duplicated dispatch. Gone. One edit inside the function that already branches on server.kind.

S2 — Third call site. Fixed for free, and pinned by two updated assertions in mcp-server-tool-sources.test.ts (…/projects/server-project/mcp).

S3 — Helper edge cases. All three closed and tested — trim() on the project id, an idempotency strip (/projects/[^/]+/mcp/?$), and trailing-slash collapse. Re-probed against the new implementation; the previously broken rows now behave.

S4 / S5 — Docs regenerated, ci (lint) green; JSDoc added.


The transport redesign holds up — I checked it rather than assuming

createRemoteMCPToolSourceFactoryWithTransport was rewritten from a construction-time decision on a static string to a per-request decision on the resolved URL. That looked like a trust widening on first read, since the old doc comment explicitly said "Invalid, unmatched, or resolver-based endpoints retain the guarded outbound path" and this PR deletes the assertion that encoded it:

-  it("keeps unmatched and dynamic endpoints on guarded transport", async () => {
-        () => "http://veryfront-api/mcp",
+  it("keeps unmatched endpoints on guarded transport", async () => {
+        () => "http://169.254.169.254/latest/meta-data",

I probed it directly. It is sound, and in fact stronger than what it replaced:

  • Hostile resolver, trusted on call 1 then internal IP on call 2 → call 2 is re-checked and forced onto the guarded path: Outbound network egress blocked for internal host: 169.254.…. Because trust is decided on the URL actually being fetched, every time, a resolver cannot smuggle anything past it. The old construction-time check could not have caught this at all.
  • Origin confusion / traversal → all rejected, zero privileged calls: veryfront-api.evil.com, …/mcp/../../../admin, :8080, scheme swap, and multi-segment project ids.

Good design. It deserves to be described in the PR rather than discovered by a reviewer.


Remaining

R1 (P2) — Query strings no longer disqualify an endpoint from the deployment-owned transport.

normalizeMcpRequestEndpoint strips url.search before the trust check, but the request is sent to the full URL including the query. The old normalizeTrustedEndpoint returned undefined on any url.search, so a query string disqualified the endpoint outright. Confirmed by probe:

endpoint: "http://veryfront-api/mcp?redirect=http://169.254.169.254&x=1"
→ PRIVILEGED:http://veryfront-api/mcp?redirect=http://169.254.169.254&x=1

Not exploitable today — the host cannot be changed this way, and the endpoint is built from env plus an encodeURIComponent'd project id. But it is a removed guard, and the commit that removed it (fix(agent): preserve query scoped MCP trust) reads as a bug fix rather than a policy change. If the query is genuinely needed, say so in the code comment and add a test pinning the new boundary — something asserting that a query cannot smuggle a different origin or path past the check. Right now the only query-bearing test is the happy path (?environment=staging).

R2 (P2) — The PR description is stale and now materially misleading.

Unchanged since the first push. It still says "fmt/lint/diff checks pass" (they didn't then) and "the focused Deno test and npm build are blocked by known local native-brand/typecheck failures" (they weren't). More importantly it describes a one-line route change, while the diff now rewrites the deployment transport trust policy in src/tool/remote-mcp.ts — the most security-relevant part of the change, and the part with the deleted assertion. CodeRabbit files it under "New Features." Anyone reviewing from the description alone will not know to look at it.

R3 (P3) — isTrustedDeploymentMcpEndpoint re-parses the allowlist on every request. It loops trustedEndpoints calling new URL(trustedEndpoint) per entry, per listTools/executeTool. Parse once into the closure at factory construction, where the entries are already being validated.

R4 (P3) — Asymmetric failure on a malformed apiMcpUrl. With a project active, new URL(apiMcpUrl) throws a bare TypeError from inside the resolver; with no project, the malformed string is returned untouched and reaches validateEndpoint, which produces the intended message. Same input, two different errors depending on unrelated state.

R5 (observation, pre-existing — not a finding against this PR). endpointBindsToolAuthorization keys on VERYFRONT_API_BASE_URL, while the MCP URL is derived from VERYFRONT_API_URL (buildVeryfrontMcpUrl, agent/service/config.ts:109), and apiBaseUrl falls back to apiUrl.replace("/graphql", "/api"). A deployment where those resolve to different paths would silently skip run-id suppression. The old exact-match code had the identical coupling, so nothing regressed — but this PR is the natural place to confirm the two envs agree in the real deployments.


88/100. Correct fix, right layer, real regression tests, verified transport boundary. Land it after updating the description to reflect what actually changed — R1 and R2 are the two I'd want addressed before merge; R3–R5 are fine as follow-ups.

@kwakayama kwakayama changed the title fix(agent): scope remote MCP tools to project fix(agent): scope hosted child MCP tools to the active project Aug 12, 2026
@kwakayama
kwakayama force-pushed the fix/project-scoped-child-mcp branch from 961dc6a to 26ccb91 Compare August 12, 2026 07:08
@kwakayama
kwakayama force-pushed the fix/project-scoped-child-mcp branch from 26ccb91 to dbd9a2c Compare August 12, 2026 07:09
@kwakayama

Copy link
Copy Markdown
Contributor Author

Re-review at dbd9a2c — 94/100 (32 → 88 → 94). Ship it.

Every item from both prior rounds is closed. I re-ran the verification independently rather than taking the commit messages at face value.

Independently confirmed

  • 27/27 CI checks pass, none failing or pending.
  • 115 passed | 0 failed locally across all six affected suites.
  • 9/9 transport evasion attempts rejected (below).
  • redirect: "error" at remote-mcp.ts:687, which substantiates the description's "redirects remain disabled" claim.

R1 — query-string trust boundary: resolved the right way

You kept the behaviour and documented the invariant, which was the option I'd have picked:

* Trusted transport is selected from the parsed scheme, origin, and path only;
* query parameters remain part of the request URL and cannot change that target.

And the new test pins exactly the boundary that comment claims — a query cannot smuggle a different origin or a different path past the check. That is the assertion that was missing.

I re-probed the hardened matcher against the final commit. All nine rejected, zero privileged calls:

veryfront-api.evil.com/projects/p1/mcp     → blocked
/projects/p1/mcp/../../../admin            → blocked
:8080, https:// scheme swap                → blocked
/projects/a/b/mcp, /projects/p1/mcp/extra  → blocked
/projects//mcp, /PROJECTS/p1/mcp           → blocked
user:pw@veryfront-api/...                  → blocked

And the hostile-resolver flip still behaves — trusted on call 1, internal IP on call 2 → Outbound network egress blocked for internal host, with only the legitimate URL reaching the privileged transport. The per-request trust decision remains the strongest part of this change.

R2 — description. Rewritten and now accurate, including the transport-policy change that the previous version omitted entirely. The validation numbers match what I measured exactly.

R3 — allowlist re-parsing. trustedEndpointUrls precomputed at factory construction. Keeping the string Set alongside it for the O(1) exact-match case is correct, not redundant — the URL loop only covers project-scoped paths.

R4 — malformed-URL asymmetry. Fixed and tested. Verified both branches now return identically, so validateEndpoint produces the standard error either way:

createProjectScopedMcpUrl("not a url", null) → "not a url"
createProjectScopedMcpUrl("not a url", "p1") → "not a url"

Non-blocking, for follow-up

F1 — projectId carries a slug on one of the three paths. The hosted path feeds taskContext.projectId into the URL segment; the runtime path feeds bootstrap.projectSlug?.trim(), locally aliased to projectId (mcp-server-tool-sources.ts:260). Two different identifier forms now land in the same /projects/{…}/mcp segment.

I looked for evidence this breaks, and it points the other way: this repo's convention for project-scoped API paths is projectRef/projects/${encodeURIComponent(projectRef)}/files, /releases, /environments, and at operations.ts:599 even a bare domain. So the route almost certainly accepts either form and this is fine in practice. Worth a rename to projectRef on the runtime path so the next reader doesn't have to derive that.

F2 — the premise is still backed only by a manual staging curl. "0 tools at /mcp, 5 at /projects/{project}/mcp" is the right measurement and I have no reason to doubt it, but nothing in-repo exercises the real control-plane route, and the validation line doesn't say which identifier form was used. Ties to F1.

F3 — endpointBindsToolAuthorization env coupling (pre-existing, carried over from my last review). It keys on VERYFRONT_API_BASE_URL, while the MCP URL derives from VERYFRONT_API_URL via buildVeryfrontMcpUrl, and apiBaseUrl falls back to apiUrl.replace("/graphql", "/api"). If those resolve to different paths in a real deployment, run-id suppression silently no-ops. The old exact-match code had the same coupling so nothing regressed — but it is a one-command check worth doing now that this code is being touched.


94/100. Three rounds took this from a 26-line change that broke six tests to a well-tested fix with a stronger transport boundary than the code started with. The remaining items are naming and confirmation, not correctness. Approve.

@kwakayama
kwakayama added this pull request to the merge queue Aug 12, 2026
@kwakayama
kwakayama removed this pull request from the merge queue due to a manual request Aug 12, 2026
@kwakayama
kwakayama added this pull request to the merge queue Aug 12, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 12, 2026
child-fork-tool-sources.ts gained a line (execution-support.ts changes
from #3632), shifting the source link for
prepareDefaultHostedChildForkSandboxToolSources from L196 to L197.
@kwakayama
kwakayama added this pull request to the merge queue Aug 12, 2026
@kwakayama
kwakayama removed this pull request from the merge queue due to a manual request Aug 12, 2026
@kwakayama
kwakayama added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 081aa81 Aug 12, 2026
33 checks passed
@kwakayama
kwakayama deleted the fix/project-scoped-child-mcp branch August 12, 2026 09:45
@kojiwakayama kojiwakayama mentioned this pull request Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human-input Maintainer action required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants