Skip to content

Agent-principal MCP OAuth resolver (type: platform) — unblocks headless-agent startup #324

Description

@initializ-mk

The urgent one. Separate from #317 (delegated / per-user) — this is the agent-principal resolver: a deployed agent authenticating to an MCP server as its own identity, not on behalf of a human. It is what actually unblocks a headless agent's mcp: no stored token — login required startup failure. In neither #317 nor #318. Authority: design-tool-registry.md §18 (§18.2 seam, §18.3 client_credentials / oauth_3lo_service, §18.4 layer split).

Problem

A deployed agent with an oauth MCP server fails at startup:

mcp: no stored token — login required: "<server>" — run 'forge mcp login <server>'

forge mcp login is laptop-only and interactive (#320) — a headless pod can't run it. #317 solves the delegated path (a human consents per session), but most deployed agents are agent-principal: they act as themselves (a service/agent identity), with no requesting human. There is no user to consent, so #317's flow can never fire for them. This is the more common and more urgent case.

What it is (§18.3)

The agent authenticates as its own identity via a resolver that needs no per-user consent:

Resolver Principal Standing credential Layer
client_credentials (2LO) the agent client secret only Forge (secret via broker when managed)
oauth_3lo_service (vaulted refresh) service identity refresh token, vaulted broker-side initializ — AgentCore-parity path

Config declares the principal, not the flow:

auth:
  type: platform      # agent-principal (contrast type: user in #317)
  max_scope: [read]
  • Standalone: where the MCP server supports 2LO / client-credentials, Forge resolves it directly (client id + secret from env/secret store) — no user, no browser, works headless.
  • Managed: the platform's token endpoint answers POST {token_endpoint} with { server } (no subject, no user_assertion) → { access_token }; the broker holds the service refresh token (vaulted), Forge never sees it.

Token-endpoint contract (shared with #317)

agent-principal:  POST {token_endpoint} { server }                    → { access_token }
delegated (#317): POST {token_endpoint} { server, subject, user_assertion } → { access_token }

Same seam (BearerToken(server, subject, session)); agent-principal simply carries no subject/assertion. This is why the seam (#317 item 1/2) is the shared prerequisite.

Why it's the urgent path

Work breakdown

Forge:

  • Reuse Epic: runtime per-user, ephemeral per-session MCP OAuth (interactive login via Slack / A2A UI) #317's seam + identity plumbing (the shared prerequisite) and the token-store interface.
  • client_credentials resolver (standalone default) — client id + secret from the secret store; 2LO token request; refresh.
  • type: platform config + validation (and the eager-connection path at startup).
  • Managed: call the platform token endpoint with { server }; consume the returned access token; never hold the service refresh token.

initializ (broker):

  • oauth_3lo_service vaulted resolver — service refresh token vaulted broker-side, resolved per invocation (§18.4 vault).
  • Scope decisions per the registry entry (A6).

Authentication ≠ authorization (§18.5)

Same as #317/#318: a valid agent-principal token is necessary, not sufficient — the PDP still governs. Writes/destructive route through DEFER regardless of token validity (A9 side-effect class); the agent acting as itself does not bypass the gates. Token never enters the agent's reasoning context; broker/egress-proxy holds it and injects at egress (§17.3).

Acceptance criteria

  • A headless agent with type: platform + a 2LO-capable server resolves a token at startup, no browser, no forge mcp login.
  • required: true + type: platform gates readiness correctly (server reachable ⇒ ready).
  • Managed mode: the service refresh token is vaulted broker-side; Forge sees only the access token.
  • Writes still route through DEFER regardless of token validity.
  • Shares Epic: runtime per-user, ephemeral per-session MCP OAuth (interactive login via Slack / A2A UI) #317's seam + token-store interface (no divergent second store).

Related

#317 (delegated type: user — shares the seam) · #318 (delegated broker / ID-JAG) · initializ/aip/mcp-delegated-identity-broker.md (add the oauth_3lo_service vaulted resolver) · #316 (discovery/DCR) · authority design-tool-registry.md §18.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestforge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)securitySecurity vulnerability fixes

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions