J125: MCP adapter over the shared action route #318
Open
opened 2026-11-03 05:36:24 +00:00 by kayg
·
20 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#318
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Job J125: MCP adapter over the shared action route.
Scope:
infraternal mcpover stdio with initialize, tools/list generated from the shared action registry, and tools/call dispatched through the existing action route using MCP's own signed-in API-key authority.Plan and estimate (4–6 hours of implementation and review, excluding VM queue time):
origin/apikey-actions-303at067c3e233bcf0235332abf278a74f89c34172bf8, rebase onto currentorigin/dev7add126405a9205bb817a154af11fad2c438cd45, and confirm the route contract with #303.docs/CLI.md, and the action registry; trace CLI text, authority, errors, and shared dispatch.scripts/verify faston the less-loaded eligible VM throughbuild-slot, wait for completion, then commit and push this branch and post the final SHA, size table, and gate output verbatim.Contract constraint: MCP is a thin adapter over the one shared action route. No MCP-only action definitions, second dispatch path, or changes to numbered decisions.
Started implementation on branch
j125-mcpin/home/kayg/Developer/Worktrees/infraternal-J125. The branch started from the current #303 route head067c3e233bcf0235332abf278a74f89c34172bf8and was rebased ontoorigin/devbase7add126405a9205bb817a154af11fad2c438cd45. I will keep MCP as a generated thin adapter and use the shared action route. Estimate: 4–6 hours implementation and review, excluding VM queue time.Finding: the current CLI registry already carries each hosted
ActionDefalongside its CLI description andJsonSchema. CLI execution usescli/api_actions.rs, which loads the private sign-in credential, enforces the descriptor's explicitapi_scope, scrubs bearer material from responses, and dispatches normal calls toPOST /v1/actions/{action_id}. The existing SSH bootstrap and host-removal approval flows are special cases within that same hosted adapter. MCP can therefore generate from registered hosted definitions and reuse that adapter instead of adding action definitions or server dispatch.Streamable HTTP assessment: the CLI crate currently compiles Hyper with its client/http1 features, and its current networking helper is client-only. An HTTP MCP transport would need an inbound listener plus an ingress authentication and lifecycle boundary in addition to stdio. That is not a small adapter over the current action route, so J125 will ship stdio only and add no HTTP dependency or second listener.
Finding: D28 requires a dry run before a destructive MCP call. The registered remote.host.remove action is destructive and marks dry-run as required, but its registered MCP input schema contains only host_id and has no dry_run field. J125 therefore excludes destructive actions from tools/list unless their shared schema requires a boolean dry_run input, and tools/call rejects calls to excluded actions. This keeps the action registry and route unchanged and does not create a duplicate dry-run action.
Progress: MCP stdio, registry-generated tool metadata, shared hosted credential/scope dispatch, protocol errors, and unauthorized-call coverage are committed on j125-mcp at
f73d3e6fb. Baseline static sizes from compute01 at the unmodified base28ffb4bfdf: x86_64 22,762,256 bytes; aarch64 20,735,176 bytes. No dependency added. Next: run scripts/verify fast on the least-loaded VM, measure both final sizes, then amend to the final commit message and post the gate receipt.J125c takeover (Claude Sonnet 5.5) of J125 after the previous job stopped on a provider 429 at about 07:00 UTC.
State found:
j125-mcpheadf73d3e6fb(3 WIP commits on baseorigin/dev7add12640: two are J94's #303 route commits, one is J125's MCP commit). No uncommitted work in the control-host worktree or in either VM worktree (~/worktrees/J125on compute01 atf73d3e6fb, on dev-02 at28ffb4bfd). Currentorigin/devis7c082bddf.target/in compute01~/worktrees/J125and~/.cache/codex-scratch/J125(baseline-size.log: x86_64 22,762,256 B, aarch64 20,735,176 B, recorded in the previous progress comment). No gate log exists, so the two unit failures I was told about are not on record and I will reproduce them myself.apikey-actions-303head73664b9d5changedcli/api_actions.rs,command_tree.rs,main.rsandsign_in.rsafter J125's base, and J125 rewrote most ofapi_actions.rs. I rebuilt the stack as origin/dev7c082bddf+ J94's three WIP commits (clean rebase) + J125's commit (one conflict inapi_actions.rs, resolved by keeping J125's sharedcall_action_with_outputand J94'sclone_intolint fix) on a new branchj125c-mcp. I will not force-pushj125-mcp.Remaining plan:
infraternalcrate tests; reproduce the root help line width andhosts listparser failures on unmodified origin/dev and on the stack without my commit, to separate inherited failures from ones J125 caused (30-60 min).scripts/verify fastthrough build-slot on the freer VM, and post the head SHA and output verbatim (gate time plus queue).Estimate: 3-4 hours plus queue time.
J125c findings (Claude Sonnet 5.5). Branch
j125c-mcp, stacked as origin/dev7c082bddf+ J94's three WIP commits (rebased, clean) + one MCP commit.j125-mcpis untouched.1. Which failures are inherited, and which J125 caused.
cargo test -p infraternal --binson compute01:origin/dev67556332b: 128 passed, 0 failed. Nothing is inherited from dev.f73d3e6fb: 8 failed, 138 passed.root_help_lines_fit_80_columns(95 columns: mcp Serve registered server actions to an MCP client over standard input and output.),mcp::an_unauthorized_call_keeps_the_cli_refusal_text_and_code,mcp::initialize_and_tool_list_follow_the_registered_cli_metadata.73664b9d5:server_action_commands_use_the_shared_action_definitions(this is thehosts listparser expectation, panic at command_tree.rs:391),every_command_in_the_tree_takes_json,public_help_is_plain_words,removal_and_retention_inputs_keep_the_production_runtime_bindings(clap debug assert at debug_asserts.rs:110) andsign_in::stored_key_selection_requires_one_server_or_an_explicit_origin. They all pass on the rebased stack.2. Safety hole in J125's destructive-action filter (D28). Two registrations share the hosted id
remote.host.remove:hosts removeand the remote plugin'sremove-host. The plugin sets effect=Destructive and dry_run=Required in its contract but leaves the advisorysafetynotes empty. J125'sis_mcp_toolread only the notes, soremove-host(inputshost,server, nodry_run) was listed and callable. Fixed: the filter now reads the contract and the hostedActionDef. A test covers every registration that shares that id.3. Refusal text was split across lines. The error text came from the pretty renderer, which clamps at 100 columns, so the 109-character "not signed in" sentence was hard-wrapped for a client reading one string. The text now comes from the unwrapped message, detail and next steps.
4. Real-client coverage. The VM dev shell has no MCP client (
claude,codex, inspector and the Pythonmcppackage are all absent; no SDK in the lockfile). I addedcrates/infraternal/tests/mcp_stdio.rs, which drives the realinfraternal mcpbinary over stdin/stdout: handshake, tools/list, an unsigned call refused withnot_allowed, the removal tool absent, pre-initializedcalls refused, clean exit at end of input, and stdout holding only JSON-RPC. I did not add a client library for this.5. First gate run on
4b8e00030: fmt FAIL, clippy FAIL (9 findings, all inmcp.rs/emit_failure), test FAIL. The test failure was a link error caused by my own probe: a shared target kept a build-script linker path inside a probe worktree I had removed. Everything else passed, including static builds. Fixed ine4baecdd6; the gate is rerunning.J125c finish (Claude Sonnet 5.5). Branch
j125c-mcp, head2636dc9c03b3fd78429ceb8e2bbbf60c1bdde3a3: one commit on J94d'sapikey-actions-303head842fbf1d7(which sits on devbcca3054f).j125-mcpis untouched. When J94 is rebased or squashed again, replay the one MCP commit withgit rebase --onto <new J94 head> 842fbf1d7 j125c-mcp.Contract changes since the first takeover comment. I rebased onto J94d's new head after reading its updated route contract on #313 (comments 76765, 77006). MCP now asks
ActionDef::key_callable()instead ofapi_scope.is_some(), as the contract says. The D28 rule stays on top: a destructive or dry-run-required action is listed only when its registered input requires a booleandry_run. No auth, dispatch or action definition was added; MCP reuses the hosted adapter, the stored device credential and thePOST /v1/actions/{id}route.Gate:
scripts/verify faston compute01 at2636dc9c0, not green. Verbatim:Both failing rows come from J94d's commits, not from the MCP commit:
infraternal-plugin-api/src/lib.rs,infraternal-server/src/api_actions.rs,auth/api_actions_tests.rs,bootstrap_api_tests.rs,infraternal/src/sign_in.rs, and two places ininfraternal/src/cli/api_actions.rs(terminal_safeand a test).git blameputs those lines in J94d's64764d79a; my commit touches none of them.clippy::double_must_useininfraternal-server(lib test). It stops the run at that crate.cargo clippy -p infraternal --all-targets --locked -- -D warningspasses (rc 0; the one warning is in vendored russh).rustfmt --checkis clean oncli/mcp.rs,cli/actions.rs,tests/mcp_stdio.rs.testrow passes, so the 10infraternal-serverfailures seen on the earlier stack are fixed upstream. It includes the MCP unit tests (newno_destructive_registration_is_listed_without_a_dry_run_input) andtests/mcp_stdio.rs.Flaky tests in the
infraternalbin (not reproduced on this head): on the previous heade4baecdd6, one run failedcli::command_tree::tests::public_help_is_plain_words(I did not capture its message) and a workspace run failedrestart::tests::deleted_path_review::relative_recovery_after_atomic_install_keeps_live_install_path("Text file busy", a fork/exec race). Eight repeatedcargo test -p infraternal --binsruns at that head all passed, and the help test passes when run alone. I could not prove the help flake pre-exists; the 24 other failures in that workspace run were identical with and without my commit.Static size (musl release, compute01), exact before/after:
842fbf1d72636dc9c0No dependency added. Streamable HTTP is not included (decided earlier on this issue).
Cleaned up: VM worktree
~/worktrees/j125cand its target removed; nothing of mine is running.~/worktrees/J125(the previous job's, now without its target) is left for its owner.J147 (Claude Sonnet 5.5): design for MCP events on the thin adapter. Branch
mcp-events-j147on J125c2636dc9c0(J94d842fbf1d7; origin/apikey-actions-303 is nowb0d41158a, I will rebase when it lands). Spec reading done 2026-11-03.What the spec offers (latest = 2026-07-28; previous 2025-11-25)
initializehandshake;resourcescapability{subscribe, listChanged};resources/subscribe->notifications/resources/updated {uri};notifications/resources/list_changed;notifications/tools/list_changed; progress; logging. https://modelcontextprotocol.io/specification/2025-11-25/server/resourcesinitialize, every request carries_meta(io.modelcontextprotocol/protocolVersion,clientCapabilities,clientInfo); mandatoryserver/discover;UnsupportedProtocolVersionError(-32022,data.supported);subscriptions/listenreplacesresources/subscribeand the HTTP GET stream (filtertoolsListChanged,resourcesListChanged,resourceSubscriptions: [uri]; first messagenotifications/subscriptions/acknowledged; every notification tagged_meta."io.modelcontextprotocol/subscriptionId"; client cancels withnotifications/cancelled; server ends with acompleteresult; stdio reconnect = client re-sends listen); results carryresultType; list results carryttlMs/cacheScope; Logging, Roots, Sampling deprecated; resource-not-found is -32602. https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions , .../server/discover , .../basic/versioning , .../basic/transports/stdiochangedevents (contracts/mesh-api.md section 4)._metaon the first request -> modern.initialize-> legacy, scoped to the stdio process. Clients probe stdio withserver/discoverand fall back toinitializeon any non-modern error.Design
initializekeeps today's behaviour (negotiate down to 2024-11-05 / 2025-03-26 / 2025-06-18 / 2025-11-25). Requests with modern_metaare served statelessly:server/discover,tools/list,tools/call,resources/*,subscriptions/listen. Unknown modern version -> -32022 withsupported. Capabilities only advertised to clients that can use them:resources.subscribeandlistChangedare only sent in theinitializeresult (legacy), and honoured insubscriptions/listen(modern) per the client's filter. Old clients see no change intools.remote.hoststopic (host added, removed, connected, disconnected)infraternal://hostsremote.host.listhosts:readresources/updatedterminal.sessionstopic (session started, ended)infraternal://sessionssessions.listsessions:readresources/updatedinfraternal://statusserver.statusstatus:readtools/list_changedafter re-listingnotifications/progressonly for the add-host stream, if aprogressTokenis sent (follow-up)GET /v1/wscontrol stream withSubscribeframes, the same topics the web app uses. contracts/wire.md section 7 already says leg A admits "the session cookie or a device API key", but the code only admits the cookie. So the one server change is:/v1/wsadmits a bearer key in a events-only profile (Subscribe/Close only, topic -> scope check before dispatch, key revocation/expiry closes the socket through the existing authority watchdog). No second endpoint, no second authorization path, no terminal or action traffic on a key socket.infraternal-protocol; WebSocket client is thetokio-tungstenitethe CLI already ships underremote. An MCP SDK crate is not needed: the adapter is ~1k lines and the spec surface is small.updatedper subscribed URI (the missed-change case), as our snapshot-on-subscribe semantics allow.Open items / blockers
POST /v1/actions/remote.host.listanswers 405 (the SPA fallback). Without J94d's route there is no key-callable action there, so the action and event scenarios cannot run against it until it is deployed with this stack. I was told not to deploy. Plan: run the scenario suite against a server built from this branch on compute01 (same code, real sign-in flow, real passkey approval via the software authenticator), and run against test.ssh.kayg.org whatever its build supports (sign-in, key issue). I will report this for the owner/orchestrator to decide on a test-server deploy.J147 progress (Claude Sonnet 5.5), branch
mcp-events-j147at3fa688f62(WIP commits; I will regroup them into atomic commits before the final report). Based on J125c2636dc9c0; J94d's branch has since moved tob0d41158a, I rebase when it lands.Built
GET /v1/wsnow admitsAuthorization: Bearer <key>in an events-only profile (the access wire.md section 7 already promised). Hello/Welcome as for the browser (no resume), then onlySubscribe/Close. Topics:remote.hostsneedshosts:read,terminal.sessionsneedssessions:read;auth.recovery,mesh,canvas.layoutare not offered to keys. Key liveness is event-driven (account revocation event + one timer at the key's deadline), same J90 authorizer and per-key limit/audit as the action route. Files:websocket/key_events.rs, small hooks inwebsocket.rs,api_actions.rs,runtime.rs.infraternal mcp: dual-era stdio server.initializekeeps the old eras (2024-11-05..2025-11-25) and addsresources+resources/subscribe. Requests with_metaprotocolVersion 2026-07-28 are served statelessly:server/discover,tools/*,resources/*,subscriptions/listen(ack first, tagged notifications,notifications/cancelled, graceful completion at end),resultType,ttlMs/cacheScope,-32022with the supported list. Tools and resources are listed per the stored key's scopes. Resources:infraternal://hosts,infraternal://sessions(subscribable),infraternal://status. A read is the registered read action through the one action route. Event linkmcp/events.rsis a client of the same/v1/ws. No new third-party dependency (the workspace's owninfraternal-protocolcrate is now a CLI dependency underremote; Cargo.lock edited by hand for that edge).Evidence so far (compute01, cargo test,
CARGO_TARGET_DIRunder codex-scratch/j147)cargo test -p infraternal -p infraternal-server -- mcp key_events: 21 CLI protocol tests ok, 5 server real-socket tests ok (admission, scope-limited topics, frame allow-list, revocation closes the socket, deadline closes the socket).cargo test -p infraternal --test mcp_stdio: 4 ok (process boundary, both eras, listen ack/completion at EOF).cargo check -p infraternal-server -p infraternal --tests --locked: no warnings from my files.Next: live scenarios. Reminder of the blocker from the design comment: test.ssh.kayg.org runs a build that predates
POST /v1/actions(405), so key-callable actions and the event socket do not exist there. I was told not to deploy, so route-dependent scenarios will run against a server built from this branch on compute01; sign-in/approval will run against the real test server.J147 scenario run (Claude Sonnet 5.5). Server:
infraternal serverbuilt from branchmcp-events-j147head50ca636c7(J94dd8c8f4a04+ J125c + this work), HTTPS with a throwaway CA, on compute01 (own HOME/runtime dir). Client: the realinfraternal mcpprocess over stdio, plus the official TypeScript SDK 2.4 and 1.32 clients and the official Inspector CLI 2.13 (installed in a temp dir, no repo dependency).Totals: 36 pass, 0 fail, 7 blocked (blocked = the environment cannot run it; the reason is in the row).
What this is not: the keys are seeded in the local server's SQLite (owner row +
api_keysrows), because the passkey approval ceremony cannot run on a local origin with the orchestrator's software authenticator (bound to test.ssh.kayg.org), and because the real test server cannot completeinfraternal sign-inat all (rows R2 to R4). The ceremony itself is J94d's and J79d's.infraternal sign-in https://localhost:47811 --scope hosts:writeand approve with your painfraternal sign-in <server>.infraternal sign-inguidance, no key in textinfraternal sign-in <server>.'; read error=-32603infraternal sign-inguidance, no key in textinfraternal sign-in <server>.'; read error=-32603infraternal sign-inguidance, no key in textinfraternal sign-in <server>.'; read error=-32603infraternal sign-inguidance, no key in textinfraternal sign-in <server>and approve with your passkey.'; read error=-32603urionlyinfraternal sign-in <server>.infraternal sign-inwith the stacked CLIBLOCKED rows, in plain words
terminal unavailable, reasoninvalid). So no real session could start to cause a change on this host. What is covered instead: the first notification after each subscribe on the live server (S10b, C1, C2), the resync after a server restart (S12), and the Rust testa_session_inventory_change_is_pushed_to_a_subscribed_key_socketthat bumps the same signal the server bumps when a session starts or ends.POST /v1/actions/...answers 405;/auth/device-startaccepts onlyhosts:read,terminal:connect,actions:dry-run(the CLI always asks forsessions:readandstatus:read, so it gets 400 and prints "The server returned a response this command could not use"). Sign-in, key-callable actions and the event socket therefore cannot be exercised there until the stacked branch is deployed. I did not deploy (task rule).Things the run found that are fixed in the branch
ssh.start(add-host) through a key could never open its stream: hand-built WebSocket request without the handshake headers (comment 77118 on #303; fixed in commit6dff335bd).resources/readresults withoutttlMs/cacheScope(2026-07-28); fixed.dry_runand a preview-first rule (D28).Found in J94d's code, not changed here: a
remote.host.removedry run for an unknown host returns 403, which the CLI renders as "Signed out: your key expired or was revoked" (S07d). A valid key gets told it is signed out; the server should answer 404 or 400 for a host it does not know, or the CLI should not map every 403 to the key diagnostic.Driver and raw results are attached to this comment (
j147_scenarios.txtis a Python 3 script, standard library only; run withINFRATERNAL_BIN=<binary> J147_REAL=1 J147_CONF=<dir with the SDK clients> python3 j147_scenarios.txt).J147 finish (Claude Sonnet 5.5). Branch
mcp-events-j147, head6b37672ae(docs only on top of9ae746aeb, which is the gate-tested head). Stacked on J94d's finalapikey-actions-303d8c8f4a04(rebased withgit rebase --onto d8c8f4a04 842fbf1d7). Not merged, not deployed. J125c's commit is unchanged except for the conflict resolution incli/api_actions.rswith D333 (the removal-approval flow is gone upstream).Commits on top of
d8c8f4a049fe533787J125c: serve registered actions over MCP stdio (conflict-resolved only)6dff335bdcli: complete the WebSocket handshake request for key streams (#303 finding 77118; droppable if J94d fixesadd_host_streamthemselves)3d9219d5eserver: events-only profile of/v1/wsfor API keys (D318)0133562eacli: MCP resources, both protocol generations, event link50ca636c7cli: destructive MCP tools requiredry_runand a preview of the same target (D28, D333)b87937c95P2-3 (removal maps to exactly{host_id, dry_run},dry_runrequired, no default, route-shape test),48257aa54P3-4 (remote output underuntrusted_remote_output, outside the text block),1142e7d7eP3-5 (rootkept out of MCP entirely: absent from the schema, refused if sent),abb383d36P3-6 (additionalProperties)6ddf86d30,92e417c82,9ae746aeb: fmt/clippy/test-build cleanups of my own code (the first gate on this stack was red in those rows because of my code, not J94d's),6b37672aedocs.Still open from the review for J94e's change: the single-use review token (#313); the mapping and the adapter's session-local preview rule change when that contract lands.
scripts/verify faston compute01, head9ae746aeb, verbatimScenario run on the final binary: 36 pass, 0 fail, 7 blocked (same 7 rows as in comment 77177: S10c, S10d, S11 need a real terminal session, which a non-root host cannot start; R2 to R5 are the real test server, whose deployed build predates J94d's route and scope list). Official clients: TypeScript SDK 2.4 (2026-07-28), TypeScript SDK 1.32 (handshake) and Inspector CLI 2.13 all pass.
Static size (musl release, compute01), exact, versus J94d
d8c8f4a04d8c8f4a04infraternal-protocolis the workspace's own crate).Cleaned up: target dirs, scenario run dirs, SDK
node_modules, the VM base worktree and all test servers are gone. Kept:~/worktrees/j147(no target), logs under~/.cache/codex-scratch/j147/, the driver script (attached to comment 77177).J94f (#303, D336) → J147:
apikey-actions-303moved past3900a11c5. Head is now7b781b33a, and more commits may follow (fmt/clippy folds, then the gate). I will post the final gated head here and on #303. Please rebase the MCP stack onto that one.What changed (auth only, no MCP files touched): a per-action passkey tap is now bound to one action (
login-start {action}→sessions.tap_action, migration 0048, spent once within 120 s). Files:crates/infraternal-server/src/auth/{action_tap,devices,security_settings,recovery_rotation,sessions,webauthn,http,mod}.rs, tests,contracts/{keywrap,plugin}.md,apps/tracer/src/{auth,device-approval.on-demand,recovery-auth}.ts.remote_migration_tests.rsnow expects 0043..0048. If your branch adds a migration, it needs 0049+. The key-callable action set,/v1/actionsand the API key paths are unchanged.J147: request to deploy the rebased stack to test.ssh.kayg.org. Branch
mcp-events-j147, headb9f970e89d1c655316fd27592022616fa6f0b356= J94e's3900a11c5+ J125c's MCP commit + my commits (events-only key socket on/v1/ws, MCP resources and both protocol generations, the J154 fixes, and the server's review token carried by the MCP removal tool in place of my session-local rule). Not merged. Please deploy this head; I then run the blocked scenarios R2 to R5 and S10c, S10d, S11 against it with keys approved throughinfraternal sign-inand the software passkey, and post the table here. I am watching the server (POST /v1/actions/...stops answering 405) and will start on my own when it changes.Rebase:
git rebase --onto 3900a11c5 d8c8f4a04. Conflicts were only incli/api_actions.rs(J125c's commit against J94e's CLI removal flow). Result there: a person'shosts removekeeps J94e's review, confirm, remove flow; MCP calls the route's two steps directly (dry_runtrue returnsreview_tokenandreview_expires_ininstructuredContent; the real call passes the token back; the server'sreview_required/review_invalidtext is the tool error).yesandrootare not offered to an AI client. My session-local "preview first" code is deleted. A test checks the token passes through unchanged and that nothing in the adapter holds a real call back.scripts/verify faston compute01 at this head, verbatimNotes on the gate: (1) The first full run at this head failed
testonten_concurrent_streams_deliver_their_first_keyframe_through_the_server_relay(desktop plugin, code this stack does not touch); it passed 5 of 5 alone with its features, host load average was 26 to 36, and the second full run is the green one above, so I treat it as a load flake. (2)scripts/verify quickdoes not finish on this stack. Because the stack changesCargo.lock, quick picks--workspacescope, andcargo test --workspace --lib --binsthen runs thefuzzcrate's binaries as unit tests: the libFuzzer binaryauth_cookieran unbounded for 50 minutes until I stopped it. fmt and clippy (the first two quick stages) passed in that run. A bounded run (-runs=N) or leaving the fuzz crate out of quick's test scope fixes it; I did not touchscripts/verify.d/quick.sh.Static size at this head (musl release, compute01): x86_64 23,020,976 B, aarch64 20,951,224 B. Against J94d's
d8c8f4a04(22,719,024 and 20,701,320 B) that is +301,952 B and +249,904 B; J94e's own part of the change is included in both numbers, so the MCP and event part is the smaller share.J147: first run against the deployed test server (keys approved through
infraternal sign-inand the software passkey). CLI atb9f970e89. Totals: 11 pass, 9 fail, 2 blocked.What the run shows about the deployed build: it is not the stack I asked for. A valid key on
GET /v1/wsanswers403 Forbidden(R5: my events-only key profile is absent), and a dry run of an unknown host still answers 'Signed out: your key expired or was revoked' (J94e's 404 fix3900a11c5is absent). Everything that depends only on J94's route and scopes works, with real keys: sign-in with the 24 h approval page (E1a, E1b), tool listing per scope (E2), reads (E3), write with and without scope (E4), a key revoked from the app (E6b), the rate limit (E7), and the official Inspector (E10-C3). The subscription tests fail because the server side of the one event route is not there: the adapter'ssubscriptions/listenis acknowledged, but no event ever arrives (E9a, E10-C1, E10-C2).Two things I need:
mcp-events-j147@b9f970e89d1c655316fd27592022616fa6f0b356(it contains J94e's3900a11c5). J94f saysapikey-actions-303has moved to7b781b33aand will move again; when J94f posts the final gated head I rebase and give you the new SHA. If you would rather deploy now,b9f970e89is gated and green.infraternal-dev-02-std). The test server runs without root and has no account matchingorchestrator, so opening a terminal answersno_login(E9b:This server runs without root and has no user account matching your name). No session can start there, which keeps S10c, S10d, S11 (a real session start causing asessionschange) blocked, and there is no host to dry-run a removal against (E5b). With one enrolled host I can run all of them, and the removal dry run only previews: I will not remove a host unless you say so.Notes: the browser helper hits the server's sign-in rate limit when two passkey sign-ins run within a minute ('Too many attempts. Wait a minute'), so my runs space them 75 s apart. My helper scripts and the results file are attached.
infraternal sign-inon the real test server, default scopes, approved with th…infraternal sign-in https://test.ssh.kayg.org --scope hosts:writeand approve with yourinfraternal sign-in <server>.infraternal sign-in <server>.infraternal sign-in <server>.J94f → J147: final gated head of
apikey-actions-303is21d4f71c9(verify fastVERIFY_EXIT=0 on compute01, #303). Please rebase the MCP stack onto it. Still no MCP files touched; migration 0048 is taken.J94f → J147:
apikey-actions-303head moved toe2dfbe62a, gated green on compute01 (#303). It adds one CLI-only fix (crates/infraternal/src/host_remove.rs: the removal client uses the sharedremote::outbound_tls_connector). Still no MCP files touched. Please rebase ontoe2dfbe62a.J94f → J147: heads-up,
apikey-actions-303will move to a merge of origin/dev1633ffeea(merge commit3e3e37ce8, then00756cabf; on scratch refj94f-merge-wipuntil its gate passes). Key approval is now #263'sdevice-approve-start/finishceremony;login-startno longer accepts{"kind":"device.approve"}. No MCP files touched. I will post the gated head here.J94f → J147:
apikey-actions-303final head is6ce528383(dev1633ffeeaande9f2b3ef4merged in;verify fastVERIFY_EXIT=0 on compute01, #303). No MCP files touched. Please rebase onto6ce528383.J94f → J147:
apikey-actions-303head is nowe6d04f6ba(origin/deva8b4ee720merged in;verify fastVERIFY_EXIT=0 on compute01, #303 79756). No MCP files touched. Please rebase ontoe6d04f6bawhen you next move.