Skip to content

Mark self-hosted MCP calls as external for activation - #351

Open
VodouAI wants to merge 1 commit into
every-app:mainfrom
VodouAI:pr/selfhost-mcp-activation
Open

VodouAI wants to merge 1 commit into
every-app:mainfrom
VodouAI:pr/selfhost-mcp-activation

Conversation

@VodouAI

@VodouAI VodouAI commented Sep 20, 2026

Copy link
Copy Markdown

On a self-hosted instance, the dashboard's "Connect your AI agent" step can never complete — no matter how many tool calls succeed.

Why

getStepStatus marks the mcp step done when either activation signal is set (dashboardSteps.ts). Neither can fire when self-hosting:

  • firstMcpAuthorizedAt is written only from oauth-provider.ts, in the hosted authorize flow. local_noauth and cloudflare_access have no OAuth flow at all.
  • firstMcpToolCallAt is written only when auth.clientId is truthy (instrumentation.ts). handleSelfHostedOpenSeoMcpRequest never sets one — self-hosted mints no client id.

So the interface asks the operator to do something they have already done, and the only way out is dismissing the card.

The change

The client-id gate exists to separate an external MCP client from a first-party caller. SAM reaches the tools in-process rather than over this transport, so every request arriving at the self-hosted handler is external by construction — a stand-in id is accurate, and the existing gate then works unmodified.

Verified on a self-hosted instance

Before, after dozens of tool calls from two different agents:

organization_activation_state  []

After, on the first call:

organization_id:         delegated-local-admin
first_mcp_tool_call_at:  2026-09-20T08:25:48.541Z

first_mcp_authorized_at stays null, which is correct — that signal belongs to the hosted OAuth path.

The transport test already pinned the exact props object, so the invariant is recorded there rather than in a new test.

Activation reads the OAuth client id to tell an external MCP client from
a first-party caller, and self-hosted mints no client id — there is no
OAuth flow. SAM reaches the tools in-process rather than over this
transport, so every request arriving here is external by construction.

Without a stand-in, neither activation signal can ever fire on a
self-hosted instance: firstMcpAuthorizedAt is written only by the hosted
authorize flow, and firstMcpToolCallAt is gated on that client id. So
"Connect your AI agent" stays outstanding on the dashboard forever, no
matter how many tool calls succeed.

Found self-hosting: organization_activation_state was still empty after
dozens of tool calls from two different agents. With this, the first
call writes the row and the step completes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant