a
Apply for Assessment Fuding

This milestone on nexus-proxy we gave the management Assistant a second front door: a
Model Context Protocol (MCP) server, so an MCP-aware client like Claude Code can list, read and
propose changes to a Nexus installation the same way the in-browser Assistant already does. It
ships on two hosts, AllCode staff reach it through the Usage API and a customer reaches it through
their own proxy’s nexus-web, and both sit behind OAuth 2.1 with a consent step. The important
part is the part that did not change: no tool approves anything. A write comes back as a proposed
change, and that change is only ever approved in the browser. This post walks through what shipped
(TASK-680 through TASK-687), why it is built this way, and where it stands now that it is live on
dev.

Why add MCP at all

The Assistant already existed as a chat surface inside /admin. The gap was reach: a developer
living in Claude Code or an MCP-capable IDE had to leave their tool, open a browser and drive the
Assistant by hand. MCP closes that gap without widening the blast radius. The same toolbox the
Assistant calls is exposed over MCP, so an agent in the client can do the read-only work, list
organizations, read a setting, draft a change, without a human ever leaving their editor. The one
thing it still cannot do is commit: the approval stays a deliberate, human, same-origin browser
action.

That is the whole design tension of this feature. An agent is most useful when it can act, and
most dangerous for exactly the same reason. The answer here is to let the agent propose freely and
to keep the commit behind a boundary an agent cannot cross.

The shape of it

Four pieces had to line up on each host:

  • An OAuth 2.1 authorization server with discovery metadata, a consent screen, and the
    authorization-code-plus-PKCE flow. It mints short-lived mcp-kind tokens (one hour) bound to the
    signed-in admin session, so an MCP token is never more powerful or longer-lived than the human
    behind it.

  • An MCP server over the existing toolbox: list, read, propose and poll. No tool in the set
    approves a change.

  • Routing and switches in both infrastructure flavours, sending /mcp, /oauth/* and
    /.well-known/oauth-* to the right service and leaving the model routes with LiteLLM. The whole
    feature is off by default (NEXUS_AGENT_MCP_ENABLED on the API,
    NEXUS_WEB_ASSISTANT_MCP_ENABLED on nexus-web); dev turns both on.

  • The session seam so an MCP session is a first-class, fenced thing: mcp-kind sessions are
    never woken or reused as browser sessions, carry their own per-call and per-address limits, and
    an org read through them stays scoped to that org.

MCP client discovers and consents with staff and customer OAuth 2.1 authorization servers, which issue tokens to each host's MCP server; both MCP servers talk only to the shared management toolbox and proposed-change ledger; only the admin browser can approve a change back into the toolbox.

Everything to the left of the toolbox is an agent proposing. The single arrow from the browser back
into the toolbox is the only path that commits, and an MCP token cannot take it.

OAuth that is never stronger than the human

The authorization server on each host is deliberately small and strict. It publishes the discovery
metadata an MCP client expects, runs the authorization-code flow with PKCE, and shows a consent
screen before it issues anything. Two properties are worth calling out because they are what keep
the token honest:

  • The token is bound to the admin’s browser session and lives one hour. It is not a standalone
    credential a client can hoard. When the admin’s session ends, the mcp token it vouches for is
    worthless. An MCP client cannot outlive or out-scope the person who approved it.

  • Consent is same-origin. The consent step enforces a same-origin referrer and a same-origin
    Referrer-Policy, so the approval cannot be driven from somewhere else. A staff admin can start
    the flow even from a signed-out browser and is walked through sign-in first, rather than silently
    failing or, worse, silently succeeding.

The customer flow is the same shape on nexus-web, which means a customer’s MCP access is minted
and checked inside the customer’s own AWS account, consistent with the rest of the proxy’s “your
account, your authority” stance.

The toolbox: list, read, propose, poll (never approve)

The MCP server exposes the management toolbox as four kinds of operation. Three of them are safe by
construction: listing organizations, reading a setting or a status, and polling a proposal you
already made. The fourth, propose, is the interesting one. A write does not mutate anything. It
produces a typed proposed change that lands in the same ledger the browser Assistant uses, and it
returns an identifier the client can poll. The change sits there, pending, until a human opens the
browser and approves it. There is no MCP tool that flips that switch, by design and proven by test:
an acting-admin attempting an approving action through MCP is refused.

Unknown tool names come back as protocol errors rather than vague failures, so an agent that asks
for something that does not exist gets a clean, machine-readable “no” instead of a guess.

Keeping the staff and customer surfaces honest

Two seams got specific hardening during review, because they are exactly where an
almost-right implementation leaks authority:

  • Org scoping. A staff org read through MCP stays scoped to the org it names. The staff MCP
    matrix exercises the seven API-313 operations across roles so a billing admin, for instance, gets
    exactly the access its role allows and no more.

  • Session isolation. mcp-kind sessions are never woken, reused or fenced as browser sessions,
    and they carry their own per-call and per-address limits so a client cannot use the MCP endpoint
    to amplify requests past what a human seat would allow.

An isolation matrix runs every route against every role, so adding an MCP operation cannot ship
without an explicit authorization answer for each role, the same gate the rest of the proxy lives
under.

Meeting developers where they already are

The point of all this is that a developer never has to leave their editor. The Assistant UIs grew a
Claude Code label and dropped the composer for MCP sessions, because in an MCP session the client is
the composer. The Help menu gained a “Use the Assistant from Claude Code” entry that prints the
exact command for this host, in both shells, so wiring up the client is copy-paste rather than
guesswork. The local dev proxy front was taught to send /mcp, /oauth/* and
/.well-known/oauth-* to nexus-web instead of LiteLLM, so the developer stack behaves like a real
deployment.

Proving it before it ships

The feature came with its own test weight, not a bolt-on:

  • SDK end-to-end suites on both hosts. A real MCP SDK client drives the staff billing admin and
    the customer nexus-web, exercising the consent hop, the callback caught from the redirect,
    unknown tools as protocol errors, and rate limits actually waited out rather than mocked away.

  • Browser-approval proofs. Separate tests confirm a signed-out browser is walked through
    sign-in before consent, and that acting-admin refusals hold, no MCP path approves a change.

  • A coverage scenario that runs one staff MCP flow through the seven API-313 operations, and an
    isolation matrix that reaches the shared stack fixture so the MCP routes are tested under the
    same role grid as everything else.

  • A WAF fix with evidence. Staff MCP OAuth tripped the managed common rule set’s
    URL-in-parameter rules; the fix lets the OAuth flow through by matching on labels rather than
    bolting on a second rule set, and the labels were checked against the dev WAF log before the
    milestone was called done.

Landing it on dev

By the end of the milestone it was live on dev with both switches on. The staff server runs over
the Usage API, the customer server over nexus-web, migration 0052 carries the schema, and the
go-sdk MCP dependency is pinned and confined by the contracts layer to just the assistant and
mcpauth modules so the SDK cannot sprawl into the rest of the codebase. CI lints the web MCP code
under the API’s rules, and the dev smoke check has to see the MCP surface before a deploy is called
good.

The open questions handed to the next session are small: confirm the WAF label behaviour against a
wider slice of real traffic, and decide whether the one-hour mcp token lifetime wants to be
tunable per installation.

Closing

The theme is the same one that runs through the whole project: usefulness and authority are
different axes, and you get to turn them independently. MCP lets an agent read and draft as freely
as it likes from inside the developer’s own tooling, while the one action that costs something, a
committed change, stays behind a human, same-origin, time-boxed approval that no token can shortcut.
That split is what lets us hand an agent the keys to look around without ever handing it the keys to
the ledger.