Skip to content

feat(server): MCP OAuth sign-in for outside agents (port upstream #16335, #16336, #16718) - #446

Merged
johnnyelwailer merged 4 commits into
mainfrom
nexi/port-mcp-oauth-outside-agents
Oct 7, 2026
Merged

johnnyelwailer merged 4 commits into
mainfrom
nexi/port-mcp-oauth-outside-agents

Conversation

@johnnyelwailer

@johnnyelwailer johnnyelwailer commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Ports upstream's MCP OAuth sign-in for outside agents so agents that Nexi Work did not start (Claude Code/Codex in a terminal, NexiWork Sync, ChatGPT/hosted bots) can connect to /mcp. Phil approves each sign-in himself, with a pairing code or a signed-in admin browser, and picks Read only or a mode ceiling. Access lasts 30 days and can be revoked in Settings → Connections.

The per-thread bearer tokens that thread agents use are unchanged. The /mcp auth middleware runs McpSessionRegistry.resolve(token) first, and only falls back to the OAuth client authenticator when that finds nothing.

Upstream commits (cherry-picked with -x, upstream authorship kept)

fork upstream PR
6bca34c 2f85686 pingdotgg/t3code#16335: every T3 MCP tool declares who may call it (required prerequisite: McpToolAccess)
5ac5bf2 2c8be58 pingdotgg/t3code#16336: outside agents sign in to the T3 MCP server with OAuth
ca0e5bc 10f39eb pingdotgg/t3code#16718: hosted agents like ChatGPT can sign in to the T3 MCP server
ad8199b (fork) chore(guard): whitelist the upstream MCP tool-access and OAuth files

No other upstream prerequisites were needed.

Conflicts and resolutions

#16335 (7 files)

  • preview/{tools,handlers}.ts, previewControls/{tools.ts,handlers.test.ts}: these conflicted only because of the #15328 server-browser tools, which are not in the fork (preview_dialog/hover/select/drag/upload). I kept the fork side, so those tools stay out, but applied #16335's own changes: PreviewToolFailure, the ThreadManagementService dependency and the access declarations.
  • toolkits/thread/handlers.ts, toolkits/core.test.ts: took upstream's structure (McpToolAccess.writesThreads). It uses the fork's unavailable because dispatchFailure doesn't exist in the fork's threadAccess.ts.
  • McpHttpServer.ts: kept the fork's PreviewBrowser provide under upstream's new toolkitRegistration/imageToolRegistration helpers. I dropped the registration blocks the merge had duplicated. T3TeamToolkitRegistrationLive now goes through toolkitRegistration.
  • Fork adaptation: the 17 T3TeamToolkit tools are now built with McpToolAccess.toLayer, and every tool is declared readsAsCaller. That means a thread caller is required and nothing more is checked: no liveness or mode check, so behaviour is the same as today. The fork's own in-handler gates still run (requireOrchestrationScope, mayCallT3TeamBrokerTool, workflowAuthorMcpScope). OAuth clients can't use the t3team tools. The failure: schemas were widened with OrchestratorMcpFailure.

#16336 (3 files, simple unions)

  • McpHttpServer.ts: kept the fork's T3_MCP_PROTOCOL (the deprecated t3team_* alias names) and exported layerMcpTransport.
  • server.ts: the t3team delegated-task and mailbox layers are provided together with McpOAuth.layerMcpClientAuthenticator.
  • __root.tsx: added /connect-agent to the fork's list of chrome-less paths.

#16718: applied cleanly.

Notes for later

  • When #15328 is ported, add its five tools as actsAsCaller and put PreviewAutomationBroker back into previewControls/tools.ts's dependencies.
  • The t3team tools that change state could move to actsAsCaller later. That would be a behaviour change, because the hidden workflow-author thread has no live run, so it's left as a separate decision.

Test plan

  • typecheck: contracts, web, shared pass. Server passes as three scoped programs that together cover all production sources and every changed or affected test. A full -p apps/server run OOMs on the box (tsgo needs ~9 GB), so CI runs it.
  • lint: 0 errors. fmt: clean.
  • vp test run apps/server/src/mcp apps/server/src/auth: 46 files / 434 tests pass.
  • guard rule, DispatchModeLimit, workflow-author scope, contracts: 14 files / 173 tests pass.
  • node t3team-additive-guard.mjs: pass.
  • Box e2e against a running server (results posted as a comment): OAuth PKCE login with read-only access through /oauth/mcp/* with a pairing code, then initialize, tools/list and t3_thread_list. A per-thread bearer token also still works, checked with a local ACP agent that calls /mcp with the token its thread session got.

juliusmarminge and others added 4 commits October 8, 2026 00:28
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2f85686)
…(#16336)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2c8be58)
…rver (#16718)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 10f39eb)
Cherry-picking #16335, #16336 and #16718 touches 38 upstream files and adds 15
unprefixed upstream modules. Record them in the additive guard's allowlists with
a reason per group in the whitelist document; no existing entry is changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T22:32:43.015622Z ad8199b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

⚠️ The latest CI run did not produce a thread transfer result for ad8199b.

This comment will update automatically after the next completed run.

@johnnyelwailer

Copy link
Copy Markdown
Owner Author

🤖 Box e2e passed. Server: this branch at ad8199b50c, fresh T3CODE_HOME, 127.0.0.1:38950.

  • Discovery: /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server return correct metadata. An unauthenticated /mcp now answers WWW-Authenticate: Bearer resource_metadata=….
  • OAuth PKCE login, Read only: done with the nexi-mcp.py helper. It registers itself, /oauth/mcp/authorize sends it on to /connect-agent (302), and /oauth/mcp/approval returns the client and redirect host. /oauth/mcp/decision approves with a pairing code from t3 auth pairing create, the loopback callback receives the code, and the token exchange returns scope=orchestration:read, expires_in=2592000.
  • Read-only client: initialize → T3 Code 0.0.45. tools/list → 92 tools, including the fork's t3_ tools. t3_project_list works, and t3_thread_list {projectId} lists the threads. t3_project_create → capability_denied (read-only). t3_thread_send → refused (read-only). t3_orchestration_status (t3team) → refused ("needs an agent running inside T3 Code").
  • Full-access client: scope=orchestration:read orchestration:operate. t3_thread_launch with a local ACP provider instance (acpRegistry, source: local) started a thread.
  • The per-thread bearer token still works: that thread's ACP agent took the per-thread credential it was given (T3_ACP_MCP_*). With it, /mcp initialize gave 200 and tools/list showed 92 tools including the t3team tools (t3_ask_user, t3_orchestration_status, t3_show_widget, t3_mywork_digest, …). t3_thread_list returned isError=false with currentThreadId set to its own thread. Its reply showed up in t3_thread_read.

CI: Typecheck (full server program), Lint, Additive guard and Check all pass. Native fingerprint diff stays queued here, just as it did on #441.

@johnnyelwailer
johnnyelwailer merged commit 0b3b3d6 into main Oct 7, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants