Epic: MCP server exposing AutoFirma sign/verify to agents (study) #28
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
EduarD3V/AutoFirme#28
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
Let coding agents sign documents via this app instead of desktop AutoFirma. STUDY-FIRST: produce
docs/MCP-DESIGN.mdwith a recommendation, then implement the recommended slice.Study questions
a) Reuse the TS engine under Node (WebCrypto via node:crypto webcrypto — PKCS12 parse + CMS/XAdES/PAdES are pure TS; PAdES byte-range + pdf-lib work in Node too)
b) Headless-browser harness (playwright drives the SPA) — heavier, but zero engine changes
Recommend one with effort estimate + risk. Read
web/src/lib/(now repo root after #26 (restructure) — adapt paths) to assess Node compatibility per module.autofirma_sign(file_b64|path, format: cades|xades|pades, cert_id, options)→ signed artifactautofirma_verify(artifact_b64)→ validity report (reuse verify module)autofirma_list_certs()→ stored cert metadata (no key material)Acceptance
mcp/dir, stdio, official @modelcontextprotocol/sdk) implementing sign(cades)+verify+list_certs, with vitest coverageIndependent of UI epics; owns
mcp/+docs/MCP-DESIGN.mdonly.bullet: coder launched on branch
issue/28-epic-mcp-server-exposing-autof(modelnan/deepseek-v4-flash).Claimed — branch
issue/28-epic-mcp-server-exposing-autof. Doing: Node-compat study ofweb/src/libper module, thendocs/MCP-DESIGN.md+ minimal stdio MCP server inmcp/(sign CAdES / verify / list_certs).Issue #28 — MCP server exposing AutoFirma sign/verify to agents (study)
Restated goal
Let a coding agent on this machine sign documents with the existing AutoFirma Web engine instead
of desktop AutoFirma. Study-first: produce
docs/MCP-DESIGN.mdwith a recommendation and rejectedalternatives, then implement the recommended minimal slice — a stdio MCP server in
mcp/exposingautofirma_sign(CAdES),autofirma_verify,autofirma_list_certs, with vitest coverage and anend-to-end check that signs a fixture through the server and verifies the output with
openssl."Done" = the design doc recommends one option with per-module evidence, the server builds and runs
over stdio, all three tools answer correctly, an e2e script proves a signature made by the server
verifies under an independent tool (
openssl cms -verify), no private key is persisted anywhere,and the repo gates stay green.
Scope
Files I create:
docs/MCP-DESIGN.md— study, recommendation, rejected alternatives, threat model.mcp/package.json,mcp/tsconfig.json,mcp/vitest.config.ts,mcp/README.mdmcp/src/config.ts— environment → config, with limits.mcp/src/input.ts—file_b64/ guardedpathsource resolution + size cap.mcp/src/certstore.ts— PKCS#12 store: load, in-memory sessions, metadata-only listing.mcp/src/audit.ts— append-only JSONL audit log of sign/verify calls.mcp/src/tools.ts— the three tool implementations against the engine (pure, testable).mcp/src/server.ts— MCP stdio wiring (@modelcontextprotocol/sdk).mcp/test/*.test.ts— vitest coverage.mcp/scripts/e2e.mjs— spawn the built server, sign, verify withopenssl, tamper check.Files I will not touch:
web/**— the engine is consumed read-only. Option (a) is chosen precisely because it needs noengine change; if a change turns out to be needed, that becomes a separate issue.
decompiled/**,original/**,extracted/**— untouched.Containerfile,.gitignore,AGENTS.md, rootdocs/architecture.md— out of scope.Tasks (each with its verification command)
web/src/lib. Evidence already gathered: engine modules use onlyglobals Node ≥20 provides (
crypto.subtle,TextEncoder/TextDecoder,btoa/atob);xades.ts,ooxml.ts,c14n.ts,verify/xades.tsneedDOMParser/XMLSerializer.Verify:
grep -rn "DOMParser\|XMLSerializer" web/src/lib→ only those four modules.mcp/package skeleton +pnpm install.Verify:
cd mcp && pnpm installandnode -e "import('@modelcontextprotocol/sdk/server/mcp.js')".docs/MCP-DESIGN.mdwith the recommendation.Verify: file exists, answers all five study questions, names the rejected alternatives.
src/input.ts+src/audit.ts+src/certstore.ts.Verify:
cd mcp && pnpm test(unit tests for path-escape rejection, size cap, listing).src/tools.ts— sign(cades)/verify/list_certs.Verify:
cd mcp && pnpm test— sign a fixture .p12 + document, verify the report isok.src/server.ts+ build. Verify:cd mcp && pnpm buildthennode scripts/e2e.mjs→ openssl verifies the signature the server produced.test -d decompiled && find decompiled -name '*.java' | grep -q .and
git diff --check.Open questions (assumption I am proceeding on)
OOXML need a DOM; PAdES needs pdf-lib (isomorphic) but the verify path for PDF is not written
yet in the repo. So the slice is CAdES-only and the other formats return an explicit
"not in this slice" result rather than a wrong answer.
cannot resolve. Assumed the boring path: bundle
mcp/src/server.tswithesbuild(singlecommand, no loader hooks) and run the bundle. Tests run under vitest, which resolves them anyway.
.p12files into a directory(
AUTOFIRME_MCP_CERTS) and passes the PIN in the environment at start. The container is alreadyencrypted at rest; the decrypted private key is imported non-extractable and lives in memory only.
a deferred follow-up.
pathinput in the slice? Yes, but only insideAUTOFIRME_MCP_WORKSPACEafterrealpath, plus an unconditional size cap;file_b64is the primary channel.Risk
@modelcontextprotocol/sdk,zod,esbuild). The issue names the SDKexplicitly, so it is required, not optional.
esbuildis the build tool for the extension-lessimports — if install fails, the fallback is a ~30-line Node module-resolution loader and the server
runs from source.
pnpm installinsidemcp/addsmcp/pnpm-lock.yaml. Committed, so the build is reproducible.decompiled/present). The real risk is a suite going red: I run theexisting
web/suite untouched before reporting, so a regression there cannot hide.bullet: coder launched on branch
issue/28-epic-mcp-server-exposing-autof(modelnan/deepseek-v4-flash).bullet: coder session died — relaunched from committed state (branch
issue/28-epic-mcp-server-exposing-autof).Claimed — branch issue/28. Re-verifying gates + e2e.
Issue #28 — MCP server exposing AutoFirma sign/verify to agents (study)
Restated goal
Let a coding agent on this machine sign documents with the existing AutoFirma Web engine instead
of desktop AutoFirma. Study-first: produce
docs/MCP-DESIGN.mdwith a recommendation and rejectedalternatives, then implement the recommended minimal slice — a stdio MCP server in
mcp/exposingautofirma_sign(CAdES),autofirma_verify,autofirma_list_certs, with vitest coverage and anend-to-end check that signs a fixture through the server and verifies the output with
openssl."Done" = the design doc recommends one option with per-module evidence, the server builds and runs
over stdio, all three tools answer correctly, an e2e script proves a signature made by the server
verifies under an independent tool (
openssl cms -verify), no private key is persisted anywhere,and the repo gates stay green.
Scope
Files I create:
docs/MCP-DESIGN.md— study, recommendation, rejected alternatives, threat model.mcp/package.json,mcp/tsconfig.json,mcp/vitest.config.ts,mcp/README.mdmcp/src/config.ts— environment → config, with limits.mcp/src/input.ts—file_b64/ guardedpathsource resolution + size cap.mcp/src/certstore.ts— PKCS#12 store: load, in-memory sessions, metadata-only listing.mcp/src/audit.ts— append-only JSONL audit log of sign/verify calls.mcp/src/tools.ts— the three tool implementations against the engine (pure, testable).mcp/src/server.ts— MCP stdio wiring (@modelcontextprotocol/sdk).mcp/test/*.test.ts— vitest coverage.mcp/scripts/e2e.mjs— spawn the built server, sign, verify withopenssl, tamper check.Files I will not touch:
web/**— the engine is consumed read-only. Option (a) is chosen precisely because it needs noengine change; if a change turns out to be needed, that becomes a separate issue.
decompiled/**,original/**,extracted/**— untouched.Containerfile,.gitignore,AGENTS.md, rootdocs/architecture.md— out of scope.Tasks (each with its verification command)
web/src/lib. Evidence already gathered: engine modules use onlyglobals Node ≥20 provides (
crypto.subtle,TextEncoder/TextDecoder,btoa/atob);xades.ts,ooxml.ts,c14n.ts,verify/xades.tsneedDOMParser/XMLSerializer.Verify:
grep -rn "DOMParser\|XMLSerializer" web/src/lib→ only those four modules.mcp/package skeleton +pnpm install.Verify:
cd mcp && pnpm installandnode -e "import('@modelcontextprotocol/sdk/server/mcp.js')".docs/MCP-DESIGN.mdwith the recommendation.Verify: file exists, answers all five study questions, names the rejected alternatives.
src/input.ts+src/audit.ts+src/certstore.ts.Verify:
cd mcp && pnpm test(unit tests for path-escape rejection, size cap, listing).src/tools.ts— sign(cades)/verify/list_certs.Verify:
cd mcp && pnpm test— sign a fixture .p12 + document, verify the report isok.src/server.ts+ build. Verify:cd mcp && pnpm buildthennode scripts/e2e.mjs→ openssl verifies the signature the server produced.test -d decompiled && find decompiled -name '*.java' | grep -q .and
git diff --check.Open questions (assumption I am proceeding on)
OOXML need a DOM; PAdES needs pdf-lib (isomorphic) but the verify path for PDF is not written
yet in the repo. So the slice is CAdES-only and the other formats return an explicit
"not in this slice" result rather than a wrong answer.
cannot resolve. Assumed the boring path: bundle
mcp/src/server.tswithesbuild(singlecommand, no loader hooks) and run the bundle. Tests run under vitest, which resolves them anyway.
.p12files into a directory(
AUTOFIRME_MCP_CERTS) and passes the PIN in the environment at start. The container is alreadyencrypted at rest; the decrypted private key is imported non-extractable and lives in memory only.
a deferred follow-up.
pathinput in the slice? Yes, but only insideAUTOFIRME_MCP_WORKSPACEafterrealpath, plus an unconditional size cap;file_b64is the primary channel.Risk
@modelcontextprotocol/sdk,zod,esbuild). The issue names the SDKexplicitly, so it is required, not optional.
esbuildis the build tool for the extension-lessimports — if install fails, the fallback is a ~30-line Node module-resolution loader and the server
runs from source.
pnpm installinsidemcp/addsmcp/pnpm-lock.yaml. Committed, so the build is reproducible.decompiled/present). The real risk is a suite going red: I run theexisting
web/suite untouched before reporting, so a regression there cannot hide.Implementation deltas (recorded after the work, for the reviewer)
The plan above is the contract the diff was written against. Three deviations, all inside the
declared scope (
mcp/+docs/MCP-DESIGN.md):AUTOFIRME_MCP_TRUSTED_ROOTSadded (mcp/src/config.ts, passed frommcp/src/tools.ts).Without it,
autofirma_verifybuilds the chain against the engine's bundled FNMT roots only, soan operator's own private CA always reads
certChain: fail. The anchors are operator-level andread once at startup: an agent must not be able to supply trust. ~10 lines, no new dependency.
autofirma_verifytakesartifact_b64/path, notfile_b64— the design doc and the issueboth name the signature input that way.
mcp/src/tools.ts+mcp/src/server.ts.mcp/dist/is no longer tracked (mcp/.gitignore, build output) and the esbuild banner wasdropped because it duplicated the shebang the source already has — the server could not start.
mcp/tsconfig.jsonmirrors the engine's needs (DOM lib) and checks only this package's files;mcp/pnpm-workspace.yamldenies esbuild's build script explicitly (pnpm exits non-zero while itis neither allowed nor denied).
No file under
web/was modified; the engine is consumed read-only from source.Verification session (commit
09e0bb5)The session above died before reporting. A second pass re-ran every gate on the committed tree
(
pnpm test29/29,pnpm check,pnpm build,pnpm e2e,web/suite 96/96,git diff --check,the
decompiled/structural gate), hand-auditedinput.ts/certstore.ts/tools.ts/server.ts,and corrected one evidence sentence in
docs/MCP-DESIGN.md: the DOM grep matchesxades.ts,ooxml.tsandverify/xades.ts(plus tests), not "exactly those four modules" —c14n.tsandverify/c14n.tstraverse DOM nodes without constructing one. Same scope, same conclusion.DONE — branch
issue/28-epic-mcp-server-exposing-autof.Commits (vs
bullet-developf7dbab8):e61b47d(operator WIP checkpoint),a0ad55ftest+e2e+trusted-anchor config,1ffc0bastudy trust anchors+evidence,09e0bb5DOM evidence correction. This session added no commit — re-verification only; tree clean.Gates (all exit 0, re-run): structural
decompiled11487 java files — pass;git diff --check— pass;cd mcp && pnpm test29/29 — pass;cd mcp && pnpm check— pass;cd mcp && pnpm build(dist/server.js 74.4 kb) — pass;cd mcp && pnpm e2e— pass (openssl verified the server's CAdES signature, tampering rejected);cd web && pnpm test96/96 — pass.Acceptance:
docs/MCP-DESIGN.mdrecommends option (a) run the TS engine under Node, rejects (b)/(c), answers all 5 study questions;mcp/stdio server (@modelcontextprotocol/sdk) with sign(cades)+verify+list_certs and vitest coverage; e2e signs a fixture and verifies with openssl; no key persistence (PKCS#12 at rest, key imported non-extractable, memory only, documented key lifecycle).Note: comment-policy curl with
BULLET_TOKEN404s on this repo (token lists only bullet/gotoi/masante-test-repo); these comments use the basic credential. Full detail in the operator report.bullet: coder session died — relaunched from committed state (branch
issue/28-epic-mcp-server-exposing-autof).bullet: implementation finished (DONE — branch
issue/28-epic-mcp-server-exposing-autof.) — moving to review.bullet: reviewer launched (model
9router/reviewer, independent of coder).DONE — branch
issue/28-epic-mcp-server-exposing-autof, tipdd74627.Commits vs
bullet-developf7dbab8:a0ad55f,1ffc0ba,09e0bb5(earlier session) +dd74627(this session, doc-only: adds the risk assessment study Q1 asks for —docs/MCP-DESIGN.md§1, 19 insertions).Gates (all exit 0): structural
test -d decompiled && find decompiled -name "*.java" | grep -q .(11487 files);git diff --check;cd mcp && pnpm test(4 files, 29 tests);mcp pnpm check;mcp pnpm build(dist/server.js 74.4 kb);mcp pnpm e2e(server signs, openssl accepts, tampering fails);cd web && pnpm test(96 tests, untouched regression). Tree clean.Note: the gate line as given to the coder skill is malformed —
test -d decompiled find decompiled -name "*.java" | grep -q .exits 2 (test: invalid test command); the&&form above is the intended check and passes.Note: #28 went to review at 17:12, before
dd74627landed. That commit touchesdocs/MCP-DESIGN.mdonly — the code review sees the same bytes.Refs #28
REVIEW: CLEAN — commit
dd74627: findings nonebullet: review CLEAN — ready to publish.
bullet: branch
issue/28-epic-mcp-server-exposing-autofnot on origin — cannot integrate.bullet: PR #32 open →
bullet-develop(auto-merge follows).bullet: PR #32 auto-merged into
bullet-develop.bullet: merged into
bullet-develop— integration gates green.