Skip to content

feat(server): serve MCP server management, status and sign-in to clients - #692

Merged
Rinat S (rsolmano) merged 19 commits into
mainfrom
mcp-stack/4-mcp-host-service
Oct 13, 2026
Merged

Rinat S (rsolmano) merged 19 commits into
mainfrom
mcp-stack/4-mcp-host-service

Conversation

@danyaberezun

@danyaberezun danyaberezun commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

#691 runs MCP servers in chats, but the host keeps everything to itself. A client can't:

  • see which servers a chat runs or whether they connected;
  • add, approve, disable or remove a server;
  • sign in to an OAuth server, unless it has pi's CLI;
  • read a result's full output or a server's log.

Edits made to mcp.json outside the app only take effect when a chat reloads for another reason.

Approach

  • Wire (protocol v83, MCP_PROTOCOL_VERSION). All methods are workspace-scoped. The browser never
    executes anything.
    • Methods: mcp.list, mcp.add / mcp.update / mcp.remove, mcp.setProjectOverride,
      mcp.setSessionOverride, mcp.approve, mcp.shareWithRepo, mcp.login / mcp.logout /
      mcp.testConnection, mcp.reconnect, mcp.readOutput, mcp.readLog.
    • The per-session mcp.status channel.
    • SessionResources.mcpServers.
    • Codes MCP_CONFIG_INVALID, MCP_PATH_UNSAFE, MCP_HANDLED_ELSEWHERE, LOGIN_NOT_OWNER.
  • Status is pi's own /mcp report. pi has no structured status API.
    • Capture. The host dispatches the session's own /mcp with a command context whose ui.notify
      alone reports to the capture. That dispatch's text never reaches the chat, and the user's own /mcp
      always prints there.
    • Startup notice. pi's unsolicited startup notice is recognised by its fixed prefix and also updates
      status.
    • Starting seed. Until pi answers, a chat reports its servers as starting: from registration, and
      again after every gated reload.
    • Snapshots. Each carries a per-session monotonic generation. Details are redacted before they
      leave the host.
    • Handled elsewhere. Where another extension owns /mcp, or -builtin:mcp is set (resolved
      without a chat open), mcp.list reports handledElsewhere.
  • Safe writes.
    • Writers are atomic and containment-checked. They keep unknown keys and indentation, and refuse
      symlinked .pi/ paths.
    • mcp.update carries expectedFingerprint. Approval carries over only from an entry that was
      already approved, so a write never approves repository content as a side effect.
    • mcp.shareWithRepo refuses to merge into unreviewed repository content.
  • Reconciliation. Only chats whose effective MCP config changed reload, through feat: upgrade pi to 1.1.0, gate session reloads and teardown, import the login env #689's gate.
    • Triggers: host mutations, an edit to the user file (a watcher on pi's agent dir), a workspace
      change under .pi/, and each agent_settled.
    • Pending reload. A busy chat shows pending-reload until its reload runs.
    • Chat disable. It applies at the next idle reload. It is refused for a server an extension
      registered.
  • Sign-in and Test connection run in a host probe session on pi 1.1.0's cancellable OAuth.
    • Owner-only. Login frames go only to the client that started the sign-in. The page must be https
      or loopback, and a paste-back is accepted only from that client.
    • One lock. The probe and a chat's /mcp login|logout <name> share one per-server sign-in lock.
    • Cleanup. A probe is cancelled when its client is reaped, or after 10 minutes.
    • Refusal. A probe is refused where MCP is handled elsewhere.
  • Redaction. One host mask (redactMcpText, maskMcpEndpoint) covers status details, config
    errors, logs and probe errors. pi reports connection errors and stderr tails verbatim.
  • Reads. mcp.readOutput serves only a result's recorded full-output file, with an expired
    state. mcp.readLog returns a server's last lines from pi's shared mcp.log (+ .1).

Changes

  • Contracts. The MCP wire types, methods, channel and codes in domain.ts / wsProtocol.ts
    (+ the v80 pin), plus a contracts SPEC section.
  • packages/server/src/agent/mcp/ (tests alongside or through the host/sign-in tests):
    • status.ts, summaries.ts, redact.ts, log.ts;
    • writers.ts, management.ts;
    • signInLock.ts, plus the lock proxy in engine.ts;
    • fixtures/oauthServer.ts.
  • packages/server/src/agent/:
    • mcpSessions.ts: status, reconciliation, watcher, listing, read output;
    • mcpSignIn.ts: probes;
    • interceptExtUiNotify in webUiContext.ts;
    • mcpServers in chatResources.ts;
    • isBuiltinExtensionEnabled in extensions.ts.
  • agentSessionManager.ts:
    • mcpSessionView / liveSessionIdsOf, dispatchSessionMcpCommand, setSessionMcpServerEnabled;
    • status hooks at registration, reload, settlement and disposal.
  • Projects. approveProjectMcpServer and setProjectMcpOverride.
  • Host.
    • host/mcp.ts (+ tests): the handlers.
    • MCP routing in provider.loginReply / provider.loginCancel.
    • server.ts wiring: the status channel, publishers, the watcher, client-reap cancellation, and the
      .pi/ change reconcile.
  • Artifacts. The probe in packages/artifact-tests checks the compiled binary: it connects a stdio
    fixture (connected, 1 tool) and reports an OAuth fixture as needs-sign-in.
  • Tests. e2e/chat-resources-wire.spec.ts expects the new mcpServers list.

Notes for reviewers:

  • The sign-in lock holder "settings" and the chat message "A sign-in for "" is already running in Settings." are
    final wording for feat: manage MCP servers in Settings #694's Settings section; until then only a wire client can start a probe, so neither is reachable
    from the app. The specs describe it as the host's sign-in probe.
  • pi's startup "MCP servers need attention" notice updates status here and still reaches the chat; feat(web): track MCP status and list each chat's MCP servers in its resources #693, which shows the
    status, turns it into status only.
  • management.ts, signInLock.ts and writers.ts are tested through host/mcp.test.ts, mcpSignIn.test.ts and
    config.test.ts.

Screenshots

Not applicable: no frontend changes. The web app adopts this wire in #693–#695.

Related issues

No tracked issue. This is part of the MCP support stack (milestone 1); review and merge in order:

  1. feat: upgrade pi to 1.1.0, gate session reloads and teardown, import the login env #689 feat: upgrade pi to 1.1.0, gate session reloads and teardown, import the login env
  2. feat: pass each project's trust to pi and name what trusting it loads #690 feat: pass each project's trust to pi and name what trusting it loads
  3. feat(server): run the MCP servers from mcp.json in chats on pi's built-in engine #691 feat(server): run the MCP servers from mcp.json in chats on pi's built-in engine
  4. feat(server): serve MCP server management, status and sign-in to clients #692 feat(server): serve MCP server management, status and sign-in to clients ← this PR
  5. feat(web): track MCP status and list each chat's MCP servers in its resources #693 feat(web): track MCP status and list each chat's MCP servers in its resources
  6. feat: manage MCP servers in Settings #694 feat: manage MCP servers in Settings
  7. feat(web): render MCP tool calls, resources and tool_search as cards #695 feat(web): render MCP tool calls, resources and tool_search as cards

Base: #691, so this diff shows only this layer. The branches remain stacked for merge order; checks on the current head are listed by GitHub.

Review hardening (2026-10-12)

Rebased onto main at 9d6ad0b2, preserving Extensions Settings/reload integration. MCP uses protocol 83; the extension catalog retains 82. Existing MCP product policy and pi engine ownership are unchanged.

  • Management rechecks current trust/workspace policy at admission; project patches merge atomically and wire values are validated. User-config symlinks and target permissions are preserved.
  • Probes use the actually admitted definition; rows expose optional effective authority and retain folded configuration errors. OAuth URLs use pi’s public callback; sign-in aliases share the credential lock.
  • Every MCP-affecting gated reload records a failure once and retains retryability, including deferred and same-value override retries after a snapshot was replaced or cleared. Successful reload alone clears that marker.
  • Command completion refreshes status; watcher recovery and diagnostic redaction are covered.

Latest verification

These results supersede the earlier counts/environment limitations below. The latest combined stack tree was compared byte-for-byte with the locally verified canonical tree; each restacked layer also received focused tests and server/web typechecks.

  • Fast gates (check:deps, boundaries, seams, spec-surface, compiler, lint) and typecheck → green; all 20 packages, including desktop. Compiler baseline was not relaxed.
  • bun run test, with isolated HOME/data/agent directories and no ambient provider credentials → all 20 package tasks plus root scripts passed. After the last UI-only preset correction, all 1,831 frontend src tests were rerun successfully; other package inputs were unchanged.
  • Final combined stack (2451693b) complete no-agent browser suite → 552 passed, all four isolated shards, using the repository’s standard CI retry configuration. Earlier local repeats exposed the unchanged touch-momentum flake, reproduced on clean main 3/8 times; no scroll code/assertion was changed or skipped. The final suite includes the preset opt-in recovery and JSON partial-add regressions.
  • Focused real-Central acceptance → 9 agent/resources/queue/tool-card cases plus 1 disabled-extension case passed; agent + queue rerun after the final retry correction → 2 passed. The complete paid-provider suite was not run.
  • Final CLI compilation (bun apps/cli/scripts/build-binary.ts) + bun run smoke:binary → passed, including the shared MCP artifact probe; vanilla-pi package parity passed under Node 22.19.0.
  • Additional review → no remaining local blockers; no suppressions were added. Spec validation retains only two pre-existing dangling task links.

Stack status (2026-10-13): rebased onto main bf4da661 on top of #723 (pi 1.2.0), which fixes the vanilla-pi parity gate broken by the
pi-codemode 1.2.0 release. #723 merges first; this stack then rebases to drop that commit. The branches remain stacked in the original
order and merge one at a time through the merge queue.

Pi 1.2.0 (2026-10-13)

Every pi adaptation in this layer was re-checked against the installed 1.2.0 package (see pi-compatibility.md in #695): the public
MCP exports, loader/writer code, shutdown ordering and startup dispatch are unchanged, so nothing is removed. Pi 1.2 widens
structuredContent to any JSON value and stops listing resources on connect (connected · N tools · resources); the status parser now
counts tools in that dotted wording (it previously read 0 for it) and both changes have fixtures.

Testing

  • Final stack on pi 1.2.0 / main bf4da661: fast gates + bun run typecheck 20/20; isolated bun run test 20/20 packages;
    bun run check:pi-packages OK (fresh npm install of pi 1.2.0 under Node 22.19); bun run e2e 551 passed + the known touch-momentum
    flaky (passed on retry); build-binary + smoke:binary passed; focused live agent/queue/chat-resources/tool-cards/subagents
    specs 11 passed; independent review of the upgrade and the new stack commits → no findings.
  • mcp.add and sharing an override now refuse a name that is already occupied in the target file by any value, including a malformed
    scalar/array/null entry a stale client never saw; previously the add overwrote it and auto-approved the replacement (review
    feat(server): serve MCP server management, status and sign-in to clients #692 (comment)).

On this PR's tree:

  • bunx turbo run typecheck --filter='!@thinkrail/desktop' → 19/19
  • bun test packages/server packages/pi-delegation packages/pi-subagents packages/pi-background-commands packages/pi-todos packages/pi-dag packages/pi-thinkrail-workflow packages/contracts packages/shared packages/artifact-tests → 2001 pass, 10 skip, 0 fail. The first run hit the known jbcentral flake "falls back to a plain runtime…"; its file passed 25/25 on rerun, and so did the full rerun
  • cd apps/web && bun test → 1580 pass
  • bun test ./scripts/ → 91 pass; bunx turbo run test --continue for every other package (desktop excluded) → 17/17 tasks
  • bun run lint → clean, no warnings; check:deps, check:boundaries, check:seams, check:spec-surface → OK
  • bun run e2e → all 8 shards passed. The first run hit the known layout.spec.ts:1061 flake; e2e/layout.spec.ts passed 30/30 on rerun
  • bun run build:binary && bun run smoke:binary → smoke OK, including the MCP artifact probe

No live spec drives an MCP server, and the complete bun run e2e:full suite was not run; the session-level MCP behaviour is covered by mcpEngine.test.ts and mcpSignIn.test.ts against pi's real engine and stdio/OAuth fixtures.

The complete bun run e2e:full suite ran on the top of the stack (#695), which contains this change: no-agent 8/8 shards; agent 53 passed, 2 failed — error-turn.live.spec.ts:35 and visualize.live.spec.ts:62, which fail on origin/main too.

Desktop: @thinkrail/desktop's typecheck could not run as configured on this machine. Its electrobun prepare step waits on a shared cache lock that another process holds. Run without prepare, cd apps/desktop && tsc --noEmit reports the same 18 electrobun-declaration errors (TS7016/TS7006/TS7031) as on origin/main and none from this change. Its bun test passes (123 pass, 1 skip, as on origin/main). This change touches no desktop file.

Checklist

  • Fast gates pass: bun run lint, bun run typecheck, bun run test
  • E2E suite passes for app-affecting changes (bun run e2e, or bun run e2e:full when touching agent behavior)
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Comment thread packages/server/src/host/mcp.ts Outdated
@danyaberezun
danyaberezun force-pushed the mcp-stack/4-mcp-host-service branch 2 times, most recently from 4d60ef1 to 4684896 Compare October 9, 2026 10:35

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline finding.

Comment thread packages/server/src/host/mcp.ts Outdated

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline finding.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline findings.

Comment thread packages/server/src/host/mcp.ts Outdated
Comment thread packages/server/src/host/mcp.ts Outdated
Comment thread packages/server/src/host/mcp.ts Outdated
Comment thread packages/server/src/agent/mcp/writers.ts Outdated

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline findings.

Comment thread packages/server/src/agent/mcp/summaries.ts Outdated
Comment thread packages/server/src/host/mcp.ts Outdated

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the mcp-stack/4-mcp-host-service branch from 4eb561a to 5d061df Compare October 9, 2026 12:12

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline finding.

@danyaberezun
danyaberezun force-pushed the mcp-stack/4-mcp-host-service branch from 5d061df to c92b2cf Compare October 9, 2026 12:24

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline finding.

@rsolmano
Rinat S (rsolmano) force-pushed the mcp-stack/4-mcp-host-service branch from aeb9718 to 4c06c9d Compare October 13, 2026 11:56

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Base automatically changed from mcp-stack/3-mcp-engine to main October 13, 2026 12:57
danyaberezun and others added 19 commits October 13, 2026 14:57
- Protocol v79: mcp.* methods, the mcp.status channel and
  SessionResources.mcpServers.
- Per-session status is pi's own /mcp report, captured by dispatch and
  redacted, with a starting snapshot until pi answers.
- Atomic, containment-checked config writers; updates carry the rendered
  fingerprint so no write approves repository content as a side effect.
- Only chats whose effective MCP config changed reload, through the gate;
  the user mcp.json and .pi/ changes are watched.
- Sign-in and Test connection run in a host probe session, owner-only,
  sharing one per-server lock with chats' /mcp login.
- mcp.readOutput and mcp.readLog; an MCP probe in the artifact harness.
…server is gone

An override-only .pi/mcp.json entry with no user-level definition behind it
is invalid, so shareMcpOverrideWithRepo re-reads the user config first and
leaves the record and the repository file untouched.
…ledElsewhere

Every mutation (add, update, remove, setProjectOverride, approve,
shareWithRepo) re-checks MCP ownership right before writing and fails with
MCP_HANDLED_ELSEWHERE, matching the read-only contract of that state.
…CP entries

approve, shareWithRepo and project-scope add/update/remove now fail with
MCP_CONFIG_INVALID for an untrusted project, whose repository entries are
not listed, so a dialog left open across a trust revoke cannot write them.
…rge project overrides

mcp.remove takes the same expectedFingerprint precondition as mcp.update for
project scope, and mcp.setProjectOverride merges the fields it carries into
the existing override instead of replacing it.
… for repository writes, probe the effective server

mcp.add/update/remove reject any scope but user/project before choosing a
path; a project-scope update or remove must carry the fingerprint the row
was rendered with (McpRenderedTarget); Test connection judges the name's
effective server; a server named __proto__ is written as an own property.
…check and write one synchronous step

A configured description now passes redactMcpText like every other text
leaving the host; a project-scope update or remove compares the fingerprint
and writes after its last await, so two requests rendered from the same
entry cannot both pass and both write.
mcpHandledElsewhereBy reads the live /mcp owners after its only await, so
a management mutation's synchronous write sees the current ownership.
…ement write

mcpOwnershipGuard resolves pi settings, then answers synchronously from the
live /mcp owners; mutateManaged runs that check and the mutation together,
so a reload handing /mcp to another extension cannot slip between them.
… check and keep __proto__ out of the record's prototype

mcp.login / mcp.testConnection sample the live /mcp owners right before the
probe starts; project MCP approvals and overrides are read through
null-prototype copies and written as own properties.
A stdio server's stderr tail reaches mcp.status verbatim from pi, so
redactMcpText now masks credential-named 'key: value' pairs too, keeping a
Bearer/Basic scheme and plain server status lines intact.
@rsolmano
Rinat S (rsolmano) force-pushed the mcp-stack/4-mcp-host-service branch from 4c06c9d to 7d5abb2 Compare October 13, 2026 12:57

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@rsolmano
Rinat S (rsolmano) added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 791d394 Oct 13, 2026
17 checks passed
@rsolmano
Rinat S (rsolmano) deleted the mcp-stack/4-mcp-host-service branch October 13, 2026 13:19
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.

2 participants