LearnNewsExamplesServices
Frontmatter
id16205
titleProvisioning templates still emit the retired stdio MCP shape after the 9-seat flip
stateClosed
labels
airefactoring
assigneesneo-opus-ada
createdAtJul 31, 2026, 2:20 AM
updatedAtAug 1, 2026, 11:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/16205
authorneo-opus-vega
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 1, 2026, 11:49 AM

Provisioning templates still emit the retired stdio MCP shape after the 9-seat flip

Closed Backlog/active-chunk-11 airefactoring
neo-opus-vega
neo-opus-vega commented on Jul 31, 2026, 2:20 AM

Context

On 2026-07-31 all nine resident seats were flipped from stdio MCP servers to the containerized plane ingress (http://127.0.0.1:3102/mc/mcp and /kb/mcp) as step 1 of #16167. The two hand-maintained provisioning templates were not part of that pass and still emit the retired shape.

Verified at origin/dev, both files, counting references to the stdio entrypoint versus the ingress port:

Template mcp-server.mjs / ai:mcp-server refs :3102 refs
.codex/config.template.toml 4 0
.claude/claude_desktop_config.example.json 4 0

So a resident provisioned from either template today lands on a transport that no longer exists on the flipped machine.

Live latest-open sweep at 2026-07-31T00:20Z: searched open issues for config.template.toml, claude_desktop_config.example, template remote steady state, and cutover cleanup local-agent-os 16167. Only #16167 itself matches, and its cleanup AC ("the complete cutover-plus-cleanup series is net-negative") does not name either template. No duplicate exists.

The Problem

The cutover runbook anticipates half of this. ai/scripts/lifecycle/local-agent-os/README.md:340:

After the live receipt is accepted, update the tracked .codex/config.template.toml to the remote steady state in the cleanup series; do not point fresh residents at the replacement before the flip.

It names only the Codex template. .claude/claude_desktop_config.example.json is absent from the runbook's cleanup list entirely, which is how it survived a procedure written specifically to retire this shape. Five of the nine flipped seats are Claude Desktop, so the unnamed template covers the majority of the fleet.

There is also a token-slot divergence introduced by the flip that the templates would silently pick a side of. See The Architectural Reality.

The Architectural Reality

  • Four live harness config formats, only two with checked-in templates. Claude Desktop (claude_desktop_config.json) and Codex (.codex/config.template.toml) are hand-provisioned from tracked templates. Kimi (.kimi-code/mcp.json) and OpenCode (opencode.jsonc) are generated by ai/services/fleet/generateKimiSeatConfig.mjs and generateOpenCodeSeatConfig.mjs. Both generators already emit the remote shape{url, bearerTokenEnvVar, enabled} and {type: 'remote', url, enabled, headers, oauth} respectively — so those two formats need no template work. The stale surface is exactly the two hand-maintained files, not four.
  • The two templates need structurally different edits. Codex supports remote MCP natively (url + bearer_token_env_var). Claude Desktop does not — it requires a local stdio bridge, and Neo ships its own: ai/mcp/client/stdioToStreamableHttp.mjs, the bridge src/ai/fleet/mcpServers.mjs:24-28 names for claude-desktop and FleetLifecycleService.mjs:1349 refuses a managed seat without. A single copied block cannot serve both.
  • Token-slot divergence, created by the flip and currently unrecorded. The nine live seats reference GH_TOKEN, chosen because it already exists per-clone with the correct per-resident value and introduces no new environment variable. The Fleet generators hard-require the other slot: generateKimiSeatConfig.mjs:339, generateOpenCodeSeatConfig.mjs:335, prepareManagedAgentWorkspace.mjs:308, and FleetLifecycleService.mjs:1502 all reject a credentialEnvVar that is not NEO_MCP_REMOTE_TOKEN. A Fleet regeneration of Iris's or Phoebe's seat would therefore rewrite them onto the other slot. Whichever slot the templates adopt is a decision that has to be written down once, not inferred twice.
  • .agents/skills/pull-request/references/mcp-config-template-change-guide.md governs template changes and is a named pr-review §8.1 gate.

Timing constraint

This must not land before #16167 §5's routed healthchecks pass. The runbook's reason is explicit — "do not point fresh residents at the replacement before the flip" — and it still binds: the nine seats are flipped but the container stack has never successfully started, so the ingress is currently unproven. Updating the templates now would point new residents at an endpoint that does not answer. This is a cleanup-series leaf, gated on the live receipt.

The Fix

  1. .codex/config.template.toml — replace the MC and KB command / args / env_vars / direct .env subsections with native url + bearer_token_env_var, preserving each server's .tools.* approval subsections.
  2. .claude/claude_desktop_config.example.json — replace the MC and KB stdio entries with Neo's own stdio↔Streamable-HTTP bridge (ai/mcp/client/stdioToStreamableHttp.mjs) invoked with --url + --token-env, carrying the bearer by reference so no raw token can appear in a tracked file.
  3. Record the token-slot decision in one place both the templates and the Fleet generators can cite, so the two provisioning paths cannot diverge again.
  4. Leave the Kimi and OpenCode generators untouched unless step 3 changes their constant.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
.codex/config.template.toml MC/KB entries flipped live Codex seats + #16167 runbook §5 native url + bearer_token_env_var; no stdio command, args, env_vars, or direct .env .tools.* approval subsections preserved verbatim this ticket + the runbook zero mcp-server.mjs / ai:mcp-server references remain; both ingress paths present
.claude/claude_desktop_config.example.json MC/KB entries flipped live Claude Desktop seats; src/ai/fleet/mcpServers.mjs:24-28 (the Fleet MCP authority naming Neo's bridge for claude-desktop) Neo's own stdio↔Streamable-HTTP bridge (ai/mcp/client/stdioToStreamableHttp.mjs) invoked with --url + --token-env, the bearer reaching it via node --env-file so a launchd-started GUI app never needs the value in its process env non-MC/KB servers untouched this ticket + learn/agentos/cloud-deployment/ClientAuthentication.md:163-183 zero stdio entrypoint references; no literal credential in the tracked file; no third-party proxy
Remote-MCP bearer slot the recorded decision from step 3 one slot name cited by both templates and the Fleet generator constant a mismatch fails a named test rather than silently regenerating a seat the decision record a test asserting template and generator agree on the slot

Acceptance Criteria

  • .codex/config.template.toml carries the native remote form for MC and KB, with every .tools.* approval subsection preserved.
  • .claude/claude_desktop_config.example.json carries Neo's own stdio↔Streamable-HTTP bridge form (ai/mcp/client/stdioToStreamableHttp.mjs) for MC and KB.
  • Neither template contains a mcp-server.mjs or ai:mcp-server reference for MC or KB; both contain the two ingress paths. Asserted with a positive control so a zero-match cannot pass on a broken pattern.
  • Neither template contains a literal credential; the bearer appears only as a variable name.
  • The token-slot decision is recorded once, and template and Fleet generator agree on it — with a test that fails if they drift.
  • Servers other than MC and KB are byte-unchanged in both templates.
  • The change lands only after #16167 §5's routed healthchecks have passed, and the PR cites that receipt.

Out of Scope

  • The Kimi and OpenCode generators, which already emit the remote shape — unless the slot decision changes their constant.
  • Bringing up the container stack, the Compose secret source, the Colima docker.sock bind, or the wake receiver. All belong to #16167.
  • The nine live seat configs. They are already flipped; this ticket is about provisioning new seats.
  • ai/mcp/server/*/config.template.mjs — a different template class (server-side config, not harness wiring) and not affected by the transport change.

Avoided Traps

  • Landing before the live receipt. Would point fresh residents at an ingress that has never answered, which is the failure the runbook's ordering exists to prevent.
  • Copying one block into both templates. Claude Desktop has no native remote MCP; only Codex does. A shared block would produce a silently non-functional Claude seat.
  • Leaving the slot divergence unrecorded. Templates on one slot and Fleet generators on another means a future regeneration silently breaks a seat, with no test to catch it.
  • Treating the surface as four templates. Two of the four live formats are generated, not templated, and are already correct. Editing them would be churn.
  • A bare zero-match as proof. grep -c returning 0 for a retired token proves nothing without a control that the pattern matches somewhere; the AC requires one.

Decision Record impact

None. This completes a cleanup boundary #16167 already prescribes and extends its list to the template the runbook omitted.

Related

  • #16167 — the cutover; owns the live receipt this ticket is gated on, and its runbook §340 prescribes half of it
  • #16039 / #16188 — landed the container-plane canonical default

Origin Session ID: 0a7f5f1d-cf12-4698-984c-17b64eea5178

Retrieval Hint: codex config template claude desktop example json stdio mcp retired remote ingress 3102 bearer slot GH_TOKEN NEO_MCP_REMOTE_TOKEN fleet generator divergence provisioning

tobiu referenced in commit ebfb1ea - "refactor(fleet): point both provisioning templates at the container-plane ingress (#16205) (#16244) on Aug 1, 2026, 11:49 AM
tobiu closed this issue on Aug 1, 2026, 11:49 AM