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
.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.
.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.
- 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.
- 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
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
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/mcpand/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:mcp-server.mjs/ai:mcp-serverrefs:3102refs.codex/config.template.toml.claude/claude_desktop_config.example.jsonSo 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, andcutover 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:It names only the Codex template.
.claude/claude_desktop_config.example.jsonis 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
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 byai/services/fleet/generateKimiSeatConfig.mjsandgenerateOpenCodeSeatConfig.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.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 bridgesrc/ai/fleet/mcpServers.mjs:24-28names forclaude-desktopandFleetLifecycleService.mjs:1349refuses a managed seat without. A single copied block cannot serve both.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, andFleetLifecycleService.mjs:1502all reject acredentialEnvVarthat is notNEO_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.mdgoverns 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
.codex/config.template.toml— replace the MC and KBcommand/args/env_vars/ direct.envsubsections with nativeurl+bearer_token_env_var, preserving each server's.tools.*approval subsections..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.Contract Ledger
.codex/config.template.tomlMC/KB entriesurl+bearer_token_env_var; no stdio command, args, env_vars, or direct.env.tools.*approval subsections preserved verbatimmcp-server.mjs/ai:mcp-serverreferences remain; both ingress paths present.claude/claude_desktop_config.example.jsonMC/KB entriessrc/ai/fleet/mcpServers.mjs:24-28(the Fleet MCP authority naming Neo's bridge forclaude-desktop)ai/mcp/client/stdioToStreamableHttp.mjs) invoked with--url+--token-env, the bearer reaching it vianode --env-fileso a launchd-started GUI app never needs the value in its process envlearn/agentos/cloud-deployment/ClientAuthentication.md:163-183Acceptance Criteria
.codex/config.template.tomlcarries the native remote form for MC and KB, with every.tools.*approval subsection preserved..claude/claude_desktop_config.example.jsoncarries Neo's own stdio↔Streamable-HTTP bridge form (ai/mcp/client/stdioToStreamableHttp.mjs) for MC and KB.mcp-server.mjsorai:mcp-serverreference 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.Out of Scope
docker.sockbind, or the wake receiver. All belong to #16167.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
grep -creturning 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
container-planecanonical defaultOrigin 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