feat(server): /mcp narrows a client's modes from T3-Mode-Limit#16653

open1
main
t3code/peer/mode-limit-header
opened18d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported just now

Part of cross-environment orchestration: an environment linked to another signs in to its /mcp as an outside agent. Every linked call has to carry the calling agent's own limits, so a plan-mode agent on the laptop can't start full-access work on the box just because the link was approved at auto.

What changes

  • /mcp reads an optional T3-Mode-Limit: <runtime>/<interaction> header, from OAuth (mcp-client) callers only.
  • The header can only narrow the client's ceiling. A broader value changes nothing, and a malformed one gets a 400 (invalid_mode_limit).
  • clientModeCeiling replaces clientRuntimeModeCeiling, adding an interaction ceiling. loadCaller and the orchestrator MCP service use it, so the access declarations and the in-lock DispatchModeLimit check enforce the narrowed modes.
  • Provider sessions ignore the header; their limits come from their thread.

Verification

  • New test in McpHttpServer.test.ts. A real /mcp, with an auto OAuth client, serves a probe tool that starts a thread through McpToolAccess.startsThreads. Results:
    • no header β†’ auto/default;
    • approval-required/plan β†’ narrowed, and escalation requests above it are refused;
    • a broader header β†’ unchanged;
    • malformed β†’ 400.
  • Mutation-checked both ways: dropping the narrowing fails the test, and so does letting a broader header widen the ceiling.
  • MCP and auth suites: 37 files, 364 tests, all passing.

Opus 5.5 via Claude Code.

πŸ€– Generated with Claude Code


Devin Review

Checks

All checks passed
Synced from GitHub

Thread transfer impact

βœ… Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB+7 B (+0.1%)6.8 KiBβœ…
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
CodexLive turn WebSocket wire1.2 KiB1.2 KiB+7 B (+0.6%)2.0 KiBβœ…
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiBβœ…
CodexLive turn messages220 (0.0%)8βœ…
ClaudeTotal thread wire5.0 KiB5.0 KiBβˆ’24 B (βˆ’0.5%)6.8 KiBβœ…
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiBβˆ’24 B (βˆ’2.0%)2.0 KiBβœ…
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiBβˆ’41 B (βˆ’0.2%)29.3 KiBβœ…
ClaudeLive turn messages21βˆ’1 (βˆ’50.0%)8βœ…

Baseline: efa5381 Β· PR result: d868b62 Β· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

End-to-end run, two real servers

Two t3 serve processes from the top of this stack, each with its own data directory, on one machine. A laptop (port 3971) and a box (port 3972). Their projects are clones of one bare origin, so they share a repository. An outside agent (OAuth with a pairing code) drives the laptop's /mcp, and real Claude Sonnet 5.5 turns run on both sides. The last part repeats the run over Tailscale HTTPS (a *.ts.net HTTPS address).

Through the link, the box refused launches above the caller's modes. A plain call at full-access got "Child runtime mode full-access is broader than parent mode auto." (the link's access was auto). The same launch at auto with T3-Mode-Limit: approval-required/plan got "Child runtime mode auto is broader than parent mode approval-required.", so the header narrowed the ceiling below the link's.

CI also showed an unused MODE_LIMIT_HEADER export (Knip). The constant now lives in McpInvocationContext, where #16684 already uses it.

Opus 5.5 via Claude Code.

Synced from GitHub

Review in 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.

  • Last reviewed commit: 0b078468c5f2610a2366bac9eac2acfb4f3a31ce
  • Latest commit, not reviewed: d868b6296c8a24bbc150ebb4d96d264316206099

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: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: d94a2616-bd0d-4db2-a460-cf472ccc8b20
πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8eadb66c31d5b146ef2f524f34c7470a6e178985 and 0b078468c5f2610a2366bac9eac2acfb4f3a31ce.

πŸ“’ Files selected for processing (1)
  • apps/server/src/mcp/OrchestratorMcpService.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

πŸ“ Walkthrough

The MCP HTTP server accepts a T3-Mode-Limit header for authenticated clients. It validates the requested modes and applies them as limits to the client’s runtime and interaction modes. MCP callers without a thread use the resulting ceilings.

Changes: OAuth Client Mode Limits

Layer / File(s)Summary
Request mode-limit contract and validation
apps/server/src/mcp/McpInvocationContext.ts, apps/server/src/mcp/McpHttpServer.ts
The invocation context defines the mode-limit header and optional narrowed modes. The HTTP server validates the header and returns HTTP 400 for malformed or unsupported values.
Effective mode ceilings and callers
apps/server/src/mcp/McpInvocationContext.ts, apps/server/src/mcp/OrchestratorMcpService.ts, apps/server/src/mcp/threadAccess.ts, apps/server/src/mcp/McpHttpServer.test.ts
clientModeCeiling independently applies runtime and interaction limits. Callers without a thread use those ceilings. The integration test checks narrowing, non-widening, escalation-denied responses, and invalid headers.

Priority: βž– Normal

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

Change: Feature

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant OAuthClient
3 participant McpHttpServer
4 participant OrchestratorMcpService
5 OAuthClient->>McpHttpServer: Send request with T3-Mode-Limit
6 McpHttpServer->>OrchestratorMcpService: Pass invocation context to handler
7 OrchestratorMcpService->>OrchestratorMcpService: Calculate runtime and interaction ceilings

Suggested reviewers: maria-rcks

Merge Risk: βšͺ Minimal Β· up to 0b078

The change narrows OAuth MCP callers’ mode limits, and both limits reach dispatch enforcement. No concrete merge-blocking risk is apparent.

πŸš₯ Pre-merge checks | βœ… 3 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the problem, implementation, affected callers, and verification results. It does not include the required Scope and approval section or an issue, approval reference, or vali…Add a ## Scope and approval section. Link the triaged issue or maintainer approval with the approval comment, or explain why this focused change qualifies for an exemption.
βœ… Passed checks (3 passed)
Check nameStatusExplanation
Title checkβœ… PassedThe title clearly describes the main change: /mcp narrows client modes using T3-Mode-Limit.
Linked Issues checkβœ… PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkβœ… PassedCheck skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, implementation, affected callers, and verification results. It does not include the required Scope and approval section or an issue, approval reference, or valid exemption rationale.

✨ Finishing Touches
πŸ“ Generate docstrings
  • Commit to this branch
  • Create a new PR
πŸ§ͺ Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot Β· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval β€” This PR adds an optional MCP mode-limit header and threads its restrictions through thread creation and dispatch authorization, while preserving existing behavior when the header is absent. Because it changes an authenticated client's authorization boundary, human review is warranted.

No code changes detected at d868b62. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Taking over this one as maintainer for batch usage-billing-05. I will preserve this branch's commits, bring it onto current main, verify request-local narrowing through the MCP access and dispatch paths, and review its linked-environment compatibility. Tests and implementation will get separate passes followed by independent reviews. This batch will not merge the PR.

Closing as superseded by the already-published maintainer replacement #21670, the foundation of the replacement linked-environment stack documented on #16684.

I compared all five changed paths in this PR against that replacement: OAuth-only header parsing, malformed-header rejection, both independent mode ceilings, access/dispatch wiring, and the HTTP regression case are retained there. The replacement also has the integrated two-server proof required by the linked-environment work. Thanks @juliusmarminge for the original design and implementation; the replacement credits that work.

Batch usage-billing-05 made no push here and stopped its redundant test pass once the overlap was confirmed. The surviving stack remains owned by batch ssh-relay-remote-15; its human permission-policy approval and platform/real two-host verification limits remain as reported there. This closure does not merge or approve that stack.

Added 1 commit

  • feat(server): /mcp narrows a client's modes from T3-Mode-Limitd868b62