Architecture + security

Specification

Source document: documentation/architecture/mcp-platform-v1.md in the source tree. Normative per RFC 2119 (MUST / SHOULD / MAY). Version: v1. Size: ~2300 LOC.

The implementation references spec sections (e.g. §7.4.2 for the Caretaker model). Spec changes go through the same review process as code.


Protocol

FieldValue
ProtocolModel Context Protocol (MCP)
Version2025-03-26
TransportHTTP + Server-Sent Events
Wire formatJSON-RPC 2.0
Lifecycleinitializetools/listtools/call
Capabilitiestools.listChanged: true
notifications/tools/list_changednot implemented (backlog)
Conformance testmcp-inspector

Transport security

TLS

FieldValue
StackQt6 QSslSocket + system OpenSSL
VersionsTLS 1.3, 1.2
Server certRSA-4096, SAN-bound, 10 years
Cert regenerationon bind address change
SAN validationRFC 6125
Cipher suitesOpenSSL defaults (distribution-hardened)
Self-implemented cryptonone

mTLS (optional)

Active as soon as ~/.config/ForgeIEC/mcp/trust/*.pem contains at least one CA cert.

FieldValue
peerVerifyModeQueryPeer
Fallback without client certbearer auth
Chain validationQt SSL stack
Subject checkagainst peers.toml (own implementation)

Federation roster

File layout

# peers.toml
[meta]
sequence_number = 17
signed_ts = "2026-05-12T18:00:00Z"
signed_by = "ForgeIEC-Team-CA"
ca_fingerprint_sha256 = "ba:fc:ef:..."
signature_b64 = "..."

[[peer]]
name = "alice@team"
fingerprint_sha256 = "ab:cd:..."
role = "author"
endpoint = "https://alice-ws.factory:7531"

Analogously revoked.toml for the revocation list.

Signature

FieldValue
AlgorithmEd25519 (preferred) or RSA-PSS-SHA256
Implementationopenssl pkeyutl shell-out
Signature payloadfile without signature_b64 line (line-oriented)
Replay protectionmonotonic sequence_number, persisted in QSettings
Distribution channelarbitrary (Git, S3, HTTP, USB)

Code path

FMcpPeerRoster::verifySignature (Studio), FMcpCaretaker::signCsr for the signing inverse. Reload via QFileSystemWatcher with 200 ms debounce.


Confirmation State Machine

Source file: FConfirmationStateMachine. Spec: §9.5.

Flow

sequenceDiagram
    participant Client as LLM client
    participant Server as MCP server
    participant FSM as State Machine
    participant Op as Operator

    Client->>Server: tools/call (project.write.add_variable)
    Server->>FSM: suspend(question, options, context)
    FSM-->>Server: pending_id
    Server-->>Client: FORGE_ERR_CONFIRMATION_REQUIRED + pending_id
    Note over Client,Op: Question appears in chat
    Op->>Client: "yes"
    Client->>Server: tools/call (editor.confirm, id, "yes")
    Server->>FSM: resume(id, "yes")
    FSM->>Server: original handler with force=true
    Server-->>Client: result of the original tool

Properties

FieldValue
Pending IDUUIDv4
Timeout5 min default, per-tool override
Bypassonly via force=true, set by the state machine itself
Auditappend-only JSONL in ~/.config/ForgeIEC/mcp_audit.log
Read APIeditor.pending_confirmations

Write access: three checks

Check 1 — Build-time

option(MCP_OVERRIDE_SECURITIES "Unlock MCP write tools" OFF)

Default OFF. Preprocessor-conditional — disabled code paths are not present in the default binary. No runtime switch.

Check 2 — State Machine

Every write tool goes through §9.5 (see above).

Check 3 — Visibility

ChannelContent
Chat logevery tool call visible
~/.config/ForgeIEC/mcp_audit.logJSONL, append-only, timestamp + tool + args + choice
initialize.instructionssecurity override banner when the build-time flag is active
warnings[] in responsesoverride banner repeated

Force path

Force settings (pinning a value independent of the program) are not accessible via MCP — no force.* tool, neither in default nor override build.

Defence layers

LayerMechanism
1 — codegen TOML-DFORCING_ENABLED cmake option of the PLC runtime
2 — anvild RPCForceVariable endpoint checks PLC build mode
3 — Studio UIF checkbox greyed out when PLC build has no force
4 — MCPno tool registered

MCP read side: monitor.snapshot returns forced=true|false per variable (Anvil gRPC is_forced field).


Human identification

Source class: FMcpFingerprintArt. Spec: §7.6.

Memorable ID

FieldValue
InputSHA-256 fingerprint (32 bytes)
Bit slicefirst 44 bits, sliced into 4 × 11 bits
WordlistBIP-39 English (2048 entries)
Formatword-word-word-word
Bit-flip sensitivity (first 44 bits)4 of 4 words change

Randomart

FieldValue
AlgorithmOpenSSH drunken bishop (ssh-keygen -lv -E sha256)
Grid17 × 9
Augmentation string" .o+=*BOX@%&#/^SE"
MarkersS = start, E = bishop end position

Surfaced in

EndpointContent
server_info.trust_store_cas[]per trust-store CA
team.list_peersper peer
team.request_certfor freshly issued cert

Caretaker model

Source class: FMcpCaretaker. Spec: §7.4.

File layout

~/.config/ForgeIEC/mcp/ca-team/
  ca.key   RSA-4096, 600 permissions
  ca.crt   X.509, CA:TRUE, 10 years
                keyCertSign + cRLSign + digitalSignature

Activation

RequirementForm
Build-timeMCP_OVERRIDE_SECURITIES=ON
Runtime flagQSettings mcp/caretaker_enabled
Modal confirmationliteral “I accept Team-CA responsibility”
File presenceca.key + ca.crt parse-valid

FMcpCaretaker::isCaretaker() returns true only if all four are satisfied.

Operations

ToolStatus
team.list_peers (Member + Caretaker)done
team.request_cert (Caretaker)done
team.revoke_peer (Caretaker)stub (revoked.toml mutation in backlog)
team.rotate_cert (Member)backlog
team.export_setup (Caretaker)backlog

All mutations through the state machine.

Multi-Caretaker (HA)

Multiple Caretakers possible. Conflict resolution via monotonic sequence_number in the signed roster.


Audit + reproducibility

AspectStatus
Audit logappend-only JSONL, no delete path
Audit fieldstimestamp, tool, args, choice, called_by
Codegen determinismbyte-identical POUS.c per .forge
Test coverage117+ tests, IEC language + 132 library blocks + multi-task + persist + force
Jitter testphysical measurement against baseline

Implementation languages

ForgeIEC Studio

C++17 + Qt6. RAII via QObject parent chain. Implicit-shared QObjects for cross-thread snapshots. No global mutable state.

MCP-layer modules:

ClassFile
FMcpServereditor/src/runtime/FMcpServer.cpp
FMcpCertManagereditor/src/runtime/FMcpCertManager.cpp
FMcpTrustStoreeditor/src/runtime/FMcpTrustStore.cpp
FMcpPeerRostereditor/src/runtime/FMcpPeerRoster.cpp
FMcpCaretakereditor/src/runtime/FMcpCaretaker.cpp
FMcpFingerprintArteditor/src/runtime/FMcpFingerprintArt.cpp
FConfirmationStateMachineeditor/src/runtime/FConfirmationStateMachine.cpp
FMcpAuditLogeditor/src/runtime/FMcpAuditLog.cpp

Runtime server

anvild: Rust + Tokio + tonic. Memory-safe by borrow checker. gRPC proto: anvil-server/proto/plc_service.proto.

IPC

iceoryx2 (Rust + C-FFI). ABI probe against type-hash drift: anvil-shared@50cb29f. Three defence layers against mismatched versions.


Standards

StandardUse
IEC 61131-3Programming language + compile path (matiec)
PLCopen XMLProject file format
RFC 6125TLS server cert SAN validation
RFC 8032 (Ed25519)Roster signature (preferred)
RFC 8017 (RSA-PSS)Roster signature (HW token interop)
BIP-39 (English)Memorable-ID wordlist
SSH ssh-keygenRandomart algorithm
MCP 2025-03-26Protocol
JSON-RPC 2.0Wire format
RFC 2119Spec normative language
TOML 1.0Configuration

Open items (as of 2026-05-12)

ItemStatus
team.revoke_peer full implementationstub
team.rotate_certbacklog
team.export_setupbacklog
OCSP / CRL handlingnot implemented
Memorable-ID typing confirmation (§7.4.2)currently yes/cancel
notifications/tools/list_changed SSEnot emitted
Hardware token (PKCS#11 / FIDO2) for CA keyroadmap
force.* tool familyPhase-3 backlog
Bulk mode (MCP-10)backlog
Caretaker toggle UI in Preferencesbacklog

Full list: project_open_backlog.md (internal).


License

AGPL-3.0-or-later for all subprojects. Source inspectable. Reproducible build via Debian CPack + signed APT repository.


Contact

Security reports + audit inquiries: blacksmith@forgeiec.io — responsible disclosure preferred.


Next