MCP remote endpoint: deploy mcp/ over Streamable HTTP (Cloudflare Worker or k3s) + link from app #46
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
EduarD3V/AutoFirme#46
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?
Goal
The MCP server (issue #28, mcp/ workspace, stdio transport) needs a REMOTE mode so external agents can reach a hosted instance. Deploy it serverless and link it from the app.
Study-first slice (docs update gates the code)
Gates
mcp/ tests green (stdio untouched), new transport tests green, build clean.
Ownership
Owns mcp/** + settings slice only. No changes to src/lib signing internals.
bullet: coder launched on branch
issue/46-mcp-remote-endpoint-deploy-mcp(modelnan/deepseek-v4-flash).bullet: coder session died — relaunched from committed state (branch
issue/46-mcp-remote-endpoint-deploy-mcp).Claimed — branch
issue/46-mcp-remote-endpoint-deploy-mcp. Doing: Streamable-HTTP remote transport for mcp/ + k3s deploy bits + MCP-endpoint settings in the app.Plan — issue #46: MCP remote endpoint (Streamable HTTP) + app link
Restated goal
mcp/today is a stdio-only MCP server (issue #28:src/server.ts, three tools, PKCS#12 keystorefrom a directory + PIN in env). #46 wants a remote mode so an external agent can reach a hosted
instance, plus a way to advertise that endpoint from the web app.
"Done" for me means:
docs/MCP-DESIGN.mdgains a Remote-transport section: Streamable HTTP (spec 2025-03-26),bearer-token auth, stateless sessions, and the decision + justification of the deploy target.
mcp/serves the same three tools over/mcpwithAuthorization: Bearer <token>, token fromenv/secret, fail-closed when unset. stdio behaviour unchanged,
pnpm testinmcp/still green.tools/call
autofirma_list_certswith token, 404 off-path) over a real socket.mcp/Containerfile,mcp/deploy/k3s.yaml) + operator commands documented.Deploy itself is operator-run.
persisted like the other client-side prefs (localStorage,
theme.tspattern). ES/EN copy.Target decision (issue asked me to decide)
Chosen: Node/
fetch-handler HTTP server, deployed as a k3s Deployment behind the existingCloudflare Tunnel. Rejected: Cloudflare Workers.
Reasons (each is a real cost, not taste):
mcp/src/certstore.tsreads a directory of.p12containers(
node:fs/promises). On Workers that becomes base64 of a signing key in an env var/secret — theexact "key material in a string the platform stores" shape the design doc (§2, §5) rejects, and it
would need a new cert-source knob + a second code path for the same store.
mcp/src/audit.tsappends JSONL to a file; Workers has nofilesystem. A signature is an act with legal weight; losing the local audit sink to gain a free
tier is a bad trade.
server that must hold a live signing key anyway. The homelab already exposes services through the
existing tunnel (
Containerfile,Caddyfile), so the deployment cost is one manifest.(Request) => Responsefetchhandler, so a Workers port later is an adapter, not a rewrite. That keeps the preferred option
reachable without paying for it now.
Stateless sessions:
sessionIdGenerator: undefined, JSON responses. One POST = one completeMCP exchange, no server-side session map, nothing to leak between callers, and it survives replica
restarts. Bearer token is re-checked on every request. Documented in the design doc.
Scope — files I will change
mcp/:src/config.ts— addtoken(AUTOFIRME_MCP_TOKEN),host(AUTOFIRME_MCP_HOST, default127.0.0.1),port(AUTOFIRME_MCP_PORT, default 8790).src/surface.ts(new) — the three tool registrations extracted verbatim fromserver.ts, onedefinition shared by both transports.
src/server.ts— same stdio behaviour, now callsbuildServer().src/http.ts(new) —createHttpHandler():/mcponly, bearer auth, stateless transport.src/server-http.ts(new) — Nodehttpentry adapting the fetch handler; exportsstartHttpServer()so tests can bind an ephemeral port.src/http-standalone.ts? no.binstaysdist/server.js.test/http.test.ts(new),test/helpers.ts(addtokento the config fixture).package.json— build both entries (--outdir=dist),start:httpscript.Containerfile,deploy/k3s.yaml(new),README.md(remote section + commands).app:
src/lib/mcp.ts(new) — endpoint/token prefs in localStorage,theme.tsshape.src/lib/mcp.test.ts(new),src/lib/App.test.ts(one dialog test).src/lib/i18n.ts— MCP copy, ES + EN.src/lib/App.svelte— help-menu entry + settings dialog (field, copy button, token hint).docs:
docs/MCP-DESIGN.md— Remote-transport section + configuration table rows + follow-ups.Explicitly NOT touched
src/lib/signing internals (cms.ts,keystore.ts,pkcs12.ts,sign.ts,verify/**, …) —nothing in the signing path changes.
web/src/lib/**is not modified:mcp/consumes it read-only (doc §1); no engine change needed.original/,extracted/,decompiled*,test/fixtures(reused as-is bymcp/test/helpers.ts).test/tools.test.ts,test/config.test.ts,test/input.test.ts,test/certstore.test.ts) — only the config fixture gains a field.Ordered tasks (each with its verifying command)
token/host/portinloadConfig;buildServer(config, store)in
src/surface.ts;server.tsuses it. →cd mcp && pnpm check && pnpm testsrc/http.ts:/mcp,Bearer, stateless transport, JSON responses,401/404/405. →
cd mcp && pnpm vitest run test/http.test.tssrc/server-http.ts(startHttpServer, ephemeral-port friendly);test drives the official
StreamableHTTPClientTransportclient over a real socket: 401 → 401,token → initialize/tools/list,
tools/callautofirma_list_certs, and a realautofirma_signwhose output
openssl… (kept: sign via HTTP is the money path — verified with the fixture CA).→
cd mcp && pnpm vitest run test/http.test.ts && pnpm buildmcp/Containerfile,mcp/deploy/k3s.yaml, README commands. →pnpm build+podman buildis operator-run; local proof = the manifest parses (python3 -c yaml.safe_load)and the built bundle starts and answers 401 without a token.
docs/MCP-DESIGN.md. →git diff --check.pnpm test src/lib/mcp.test.ts src/lib/App.test.tscd mcp && pnpm check && pnpm test && pnpm build; rootpnpm test,pnpm check,pnpm build;git diff --check.Open questions / assumptions (unattended: stated, then proceeded)
POST /api/v1/repos/EduarD3V/AutoFirme/issues/46/commentsreturns 404, and so does every read of that repo, while the token does authenticate as
gotoi-agent(which seesEduarD3V/{bullet,gotoi,masante-test-repo}). Assumption: the agent hasno access to AutoFirme; the claim/plan/done comments therefore live in this plan file and the
report instead. No retry loop.
store — the field is persisted in localStorage like the theme, and the UI says so ("stored in this
browser; the app never sends it anywhere"). Making it a vault item (PIN-sealed, like identities)
is a bigger diff and not requested.
Cloudflare Tunnel and bearer-authenticated; the doc states plainly that whoever holds the token can
sign with the loaded certificate, and that TLS terminates at the tunnel.
Risk and fallbacks
WebStandardStreamableHTTPServerTransportneeds unbundled SDK internalsStreamableHTTPServerTransport(Node flavour) with the same handler contract; it takesreq/res-shaped objects, so wrap instead of rewritingdist/server.js(thebin)src/server.ts→dist/server.js) and verify withpnpm e2enavigator.clipboardpnpm check(svelte-check) rejects new copy keyscopy.esandcopy.enin the same commitDONE — branch
issue/46-mcp-remote-endpoint-deploy-mcp, commitsb4f9f7fd,8be1756d,a6897453,7a0f6bd8(not pushed; orchestrator publishes).Gates (all green, re-run this session):
mcp/: pnpm check (tsc) 0 errors; pnpm test 35/35 (stdio untouched, new test/http.test.ts green); pnpm build + pnpm e2e ok (openssl verifies the signature, tamper rejected).build/; git diff --check clean.Delivered: Streamable-HTTP remote transport (
/mcp, stateless, bearer-token, fail-closed without AUTOFIRME_MCP_TOKEN) with identical tool surface; k3s manifest + Containerfile behind the existing Cloudflare Tunnel; MCP-DESIGN.md §3 remote section incl. why-not-Workers (issue preferred Workers — decision + justification there, handler is runtime-neutral(Request)=>Responseso a Worker port later is an adapter); app settings dialog with endpoint field, copy button, token hint (ES/EN). Deploy itself operator-run; commands in mcp/README.md.Note: the plan comment was posted before this line; its "forge comments unwritable" assumption was wrong — Bearer token 404s, HTTP Basic auth works (claim/plan/done posted via -u). Corrected in the plan file.
bullet: coder session died — relaunched from committed state (branch
issue/46-mcp-remote-endpoint-deploy-mcp).bullet: implementation finished (DONE — branch
issue/46-mcp-remote-endpoint-deploy-mcp, commitsb4f9f7fd,8be1756d,a6897453,7a0f6bd8(not pushed; orchestrator publishes).) — moving to review.bullet: reviewer launched (model
9router/reviewer, independent of coder).REVIEW: FIXED 1 — findings: test oracle xmlsec1Verify (
7a0f6bd8) mapped a literal FAIL verdict (pre-1.3 xmlsec1 wording) to OK — inverted failure path, fixed8d710279; UNFIXED: App.test.ts wrong-PIN test flaked once under full parallel load (passes in isolation and on rerun) — timing flake, not traced to this diffbullet: fixer launched (round 1/2).
FIXED: applied 2 — #1 xmlsec oracle inverted mapping: fix landed
8d710279, regression testee2bd310(src/test/fixtures.test.tspins the 1.3.xVerification status:,status:prefix, bare OK/FAIL and no-verdict paths with spawnSync stubbed — red against the pre-fix mapping, green after, 4 tests); #2 PBKDF2 UI flake under parallel load:633018bcsets testing-library asyncUtilTimeout 15s once insrc/test-setup.ts(wrong-PINfindByRoleand CertPickerwaitForraced the 1s default; three full-suite runs now 148/148, previously 2 of 3 failed). UNFIXED: none. Gates re-run: rootpnpm test148/148 x3,pnpm check0 errors/0 warnings (1327 files),pnpm buildok,mcp pnpm test35/35. (Refs #46)bullet: fixer round done — fresh reviewer re-checking.
REVIEW: CLEAN — commit
633018bc: findings none; prior finding #1 regression-pinned atee2bd310(proven: reverting the mapping fails 2/4 tests), flake finding #2 fixed at633018bc(root suite 148/148 ×3), all gates green (root test/check/build, mcp check/test/build/e2e, git diff --check), tree cleanbullet: review CLEAN — ready to publish.
EduarD3V referenced this issue2026-10-15 08:44:01 +02:00
bullet: PR #57 open →
bullet-develop(auto-merge follows).bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.EduarD3V referenced this issue2026-10-15 08:47:32 +02:00
bullet: PR #58 open →
bullet-develop(auto-merge follows).bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/46-mcp-remote-endpoint-deploy-mcp. Doing: re-apply MCP remote Streamable HTTP endpoint + settings link onto bullet-develop, resolve conflicts.RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — origin/main
abda84d9is already an ancestor of bullet-developd1b8eaf5, rolling PR #48 reports mergeable=true; the live conflict is PR #58 issue/46→bullet-develop in src/lib/App.svelte + src/test/fixtures.ts, owned by the conflict-solver)DONE — no new commits (merge was a no-op: trunk contained). Gates: git diff --check clean, no conflict markers in HEAD tree, push HEAD:bullet-develop → "Everything up-to-date".
INTEGRATED: #46 merged into bullet-develop (conflicts resolved: kept ours App.svelte Switch import + consolidated xmlsec1 regex; dropped dup test-setup configure block; added #40 status field to mcp xades stub)
DONE
c7d63469— gates: git diff --check clean; mcp pnpm test 35/35; root vitest 191 passed/1 skipped (fixtures.test.ts, mcp.test.ts, App.test.ts included); mcp tsc --noEmit 0 errors; mcp esbuild ok; pnpm check 0 errors; pnpm build ok. Refs #46