You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The open edition's AI surface is MCP-only (BYO-AI via @objectstack/mcp, see content/docs/ai/agents.mdx). Today the HTTP MCP endpoint (/api/v1/mcp) authenticates only with API keys (x-api-key / Authorization: ApiKey, hashed at rest, scoped, fail-closed — packages/core/src/security/api-key.ts). That works for headless agents and developers, but it blocks the mainstream human-connected clients:
claude.ai custom connectors and the Anthropic connector directory require the MCP-spec authorization flow (OAuth 2.1).
Claude Desktop / Claude Code offer a much smoother connect experience over OAuth (browser login as yourself) than hand-copying header config.
Every user currently needs an admin-minted API key before they can connect anything.
The MCP authorization spec is deliberately decentralized: the client discovers the authorization server via .well-known metadata on the MCP host itself, self-registers via Dynamic Client Registration (RFC 7591), and runs an authorization-code + PKCE flow. This means self-hosted / private deployments work out of the box — each deployment is its own authorization server; nothing is pre-registered with Anthropic or with any central ObjectStack service.
Goal
Add spec-compliant OAuth 2.1 authorization to the MCP endpoint so any MCP client can connect self-serve, while keeping API keys as the headless track.
Design decisions (rationale lives here + changeset; no separate ADR per repo convention)
Each deployment is its own authorization server. Back the flow with the embedded better-auth instance (it already owns login/sessions). No central authority, no dependency on cloud — the community/self-hosted edition is the primary target, not an afterthought.
Discovery endpoints: serve .well-known/oauth-protected-resource (resource metadata pointing at the AS) and .well-known/oauth-authorization-server (AS metadata) from the same origin as /api/v1/mcp.
Dynamic Client Registration is mandatory. Since every deployment is a distinct AS, clients cannot ship pre-registered client IDs. DCR is the enabler for "one generic client config works against any deployment".
PKCE required; TLS required (localhost exempt per OAuth 2.1). Intranet deployments with an internal CA work for local clients (Claude Code/Desktop); claude.ai web connectors additionally need the endpoint publicly reachable — a network decision, out of our hands, but must be documented.
Token → ExecutionContext mapping: a valid access token resolves to the same principal-bound ExecutionContext used today (via resolveAuthzContext); RLS and permissions apply unchanged. OAuth adds a second provenance for the principal, not a second authz model.
Dual-track, additive: API keys stay exactly as they are (CI, headless agents, environments without TLS). Nothing about the existing header auth changes.
Fail-closed parity with API keys: unknown/expired/revoked tokens, missing scopes, or ambiguous state → 401/403, never fallback to anonymous.
Scopes: map coarse scopes to the exposed tool families (data read, data write, actions). Keep the initial scope set minimal; finer grades can follow.
better-auth wired as the OAuth 2.1 AS: DCR, authorization-code + PKCE, token issuance/refresh, revocation
Bearer-token authentication on /api/v1/mcp, resolving to the caller's ExecutionContext (single-sourced through resolveAuthzContext, per the authz-resolver lint gate)
401 responses advertise the discovery metadata (WWW-Authenticate per MCP spec) so clients can bootstrap the flow
Scope enforcement at the tool-dispatch layer, fail-closed
End-to-end verification: connect a real MCP client (Claude Code) to a dev instance via the OAuth flow
Docs to update on completion (acceptance criteria)
packages/mcp/README.md — connect section: OAuth as the human-client path, API key as the headless path
packages/mcp/src/skill.ts — the generated SKILL.md "Connect" section currently documents only x-api-key; describe both tracks
content/docs/ai/agents.mdx — BYO-AI connect instructions per client (claude.ai / Claude Desktop / Claude Code), incl. the private-deployment reachability note (local clients reach intranet; claude.ai web needs public HTTPS)
content/docs/getting-started/build-with-claude-code.mdx — update the connect snippet
Regenerate reference docs if any spec schema changes
Out of scope (follow-ups, tracked separately)
Official Claude Code plugin bundling the generic skill + ${OBJECTSTACK_MCP_URL} MCP config (executes ADR-0036 Amendment C; one plugin serves every deployment — URL is the only per-customer input)
"Connect an agent" page in Setup (objectui): mint key / show per-client snippets / download SKILL.md
Anthropic connector directory submission (depends on this issue)
Context
The open edition's AI surface is MCP-only (BYO-AI via
@objectstack/mcp, seecontent/docs/ai/agents.mdx). Today the HTTP MCP endpoint (/api/v1/mcp) authenticates only with API keys (x-api-key/Authorization: ApiKey, hashed at rest, scoped, fail-closed —packages/core/src/security/api-key.ts). That works for headless agents and developers, but it blocks the mainstream human-connected clients:The MCP authorization spec is deliberately decentralized: the client discovers the authorization server via
.well-knownmetadata on the MCP host itself, self-registers via Dynamic Client Registration (RFC 7591), and runs an authorization-code + PKCE flow. This means self-hosted / private deployments work out of the box — each deployment is its own authorization server; nothing is pre-registered with Anthropic or with any central ObjectStack service.Goal
Add spec-compliant OAuth 2.1 authorization to the MCP endpoint so any MCP client can connect self-serve, while keeping API keys as the headless track.
Design decisions (rationale lives here + changeset; no separate ADR per repo convention)
.well-known/oauth-protected-resource(resource metadata pointing at the AS) and.well-known/oauth-authorization-server(AS metadata) from the same origin as/api/v1/mcp.ExecutionContextused today (viaresolveAuthzContext); RLS and permissions apply unchanged. OAuth adds a second provenance for the principal, not a second authz model.Scope
.well-knowndiscovery endpoints (protected-resource + authorization-server metadata)/api/v1/mcp, resolving to the caller'sExecutionContext(single-sourced throughresolveAuthzContext, per the authz-resolver lint gate)WWW-Authenticateper MCP spec) so clients can bootstrap the flowDocs to update on completion (acceptance criteria)
packages/mcp/README.md— connect section: OAuth as the human-client path, API key as the headless pathpackages/mcp/src/skill.ts— the generatedSKILL.md"Connect" section currently documents onlyx-api-key; describe both trackscontent/docs/ai/agents.mdx— BYO-AI connect instructions per client (claude.ai / Claude Desktop / Claude Code), incl. the private-deployment reachability note (local clients reach intranet; claude.ai web needs public HTTPS)content/docs/getting-started/build-with-claude-code.mdx— update the connect snippetOut of scope (follow-ups, tracked separately)
${OBJECTSTACK_MCP_URL}MCP config (executes ADR-0036 Amendment C; one plugin serves every deployment — URL is the only per-customer input)SKILL.md