End-to-end PoC demonstrating Agent Context Token authorization and Execution Context Token accountability over MCP tool calls, using a LangGraph agent with ES256-signed JWT tokens and DAG verification.
ACT + ECT + MCP + LangGraph — end-to-end PoC
A working demonstration of draft-nennemann-act-01 (Agent Context Token)
and draft-nennemann-wimse-ect-01 (Execution Context Token) in a realistic
agent stack:
- a LangGraph ReAct agent driven by a local Ollama LLM;
- talking over MCP streamable-HTTP to a FastMCP server;
- every request carries an ACT mandate, a per-call ECT, and an
RFC 9421 HTTP signature with the
wimse-audparameter fromdraft-ietf-wimse-http-signature-03; - the server rejects any request where ACT / ECT / HTTP-signature / capability / body-hash binding fails;
- a verifier CLI replays the run's ledger, re-runs the two refimpls, and prints the resulting DAG.
Why this exists
The two drafts (ACT and ECT) claim to fit together — ACT giving the
lifecycle (mandate → execution record) and ECT giving the per-call
execution context on the wire. This PoC proves the claim end to end:
the same refimpls that ship in workspace/packages/{act,ect}/ are the
only crypto/verification layer used here. There is no token forgery
shortcut.
Requirements
uv(install)- Local Ollama with a chat model pulled (
qwen3:8bby default) - Python 3.11+ (uv will fetch one if missing)
Run the demo
./demo.sh
This script:
- Syncs deps (uv installs the sibling
ietf-actandietf-ectpackages in editable mode, plusmcp,langgraph,langchain-ollama,langchain-mcp-adapters,fastapi, …). - Launches the MCP server on
127.0.0.1:8765. - Runs the agent (
poc-agent) against it with a canned research task. - Runs the verifier (
poc-verify) over the ledger the agent emitted. - Shows the last five server-audit entries.
Expected tail of output:
mandate verified jti=64f5ec87
ects verified n=7 (tool-calls=2, session=5)
record verified jti=64f5ec87 status=completed
ect-dag wellformed every pred is the mandate or a prior ECT
Run
===
mandate 64f5ec87 task='Search for quantum entanglement, …'
iss=user sub=agent aud=mcp-server
cap=['mcp.session.initialize', 'mcp.session.list_tools', 'mcp.session.other', 'mcp.search', 'mcp.summarize']
Tool-call ECT DAG:
ect 73af4cd3 exec_act=mcp.search pred=['64f5ec87']
ect 0e3ffa01 exec_act=mcp.summarize pred=['64f5ec87', '73af4cd3']
ACT Phase 2 record:
jti=64f5ec87 exec_act=mcp.summarize
status=completed pred=[]
inp_hash=… out_hash=…
The mandate jti and the record jti are identical — this is ACT §3.2: the Phase 2 token records the same task it started as. The tool-call ECT DAG captures the per-HTTP-request ordering.
Architecture
user ──(mints ACT mandate)──► agent
│
│ create_react_agent(ChatOllama, tools)
▼
┌──────────────────────────────┐
│ LangGraph ReAct loop │
│ LLM decides tools to call │
│ │
│ langchain-mcp-adapters │
│ ↑ streamable_http session │
└──────────┬───────────────────┘
│
httpx.AsyncClient with event hooks
│
┌───────────▼─────────────┐
│ on_request: │
│ - mint ECT(inp_hash) │
│ - RFC 9421 sign │
│ - attach Authorization │
│ + Wimse-ECT + sig │
└───────────┬──────────────┘
│
POST /mcp
│
┌───────────▼──────────────┐
│ FastMCP streamable-http │
│ + ActEctAuthMiddleware │
│ verifies: │
│ ACT mandate │
│ ECT │
│ HTTP-signature │
│ inp_hash == body │
│ exec_act in cap │
│ ECT.iss == sub │
│ then dispatches tool │
└──────────────────────────┘
Files
src/poc/keys.py— ES256 keys for the three PoC identities (user,agent,mcp-server).src/poc/tokens.py— thin wrappers aroundietf-actandietf-ectthat fix the PoC's shape.src/poc/http_sig.py— minimal RFC 9421 signer/verifier covering@method,@target-uri,content-digest,wimse-ect, with thewimse-audmetadata parameter from http-signature-03.src/poc/server.py— FastMCP server with ACT + ECT + signature middleware. Writeskeys/server-audit.jsonl.src/poc/agent.py— LangGraph + Ollama agent. Writeskeys/ledger.jsonl— one mandate, N ECTs, one final ACT record.src/poc/verify_cli.py— ledger verifier, prints the DAG.tests/— pytest suite (see below).
Non-goals
- No real LLM API costs (Ollama is local).
- No distributed SCITT anchoring — server audit log is a plain JSONL.
- No Go-side client in this PoC; Python ↔ Python. Go refimpl lives in
workspace/drafts/ietf-wimse-ect/refimpl/go-lang/. - The PoC uses a single mandate per run and a single Phase 2 record
(ACT §3.2 jti preservation). If you want a multi-record DAG of ACT
tasks, you'd need to exercise the delegation machinery
(
act.delegation.create_delegated_mandate) — this PoC does not, to keep the wire story tight.
Tests
uv run pytest
Covers:
- Minting + round-trip verification of each token type.
- HTTP-signature round-trip.
- Middleware rejection paths (missing headers, wrong audience, stolen ECT on a mutated body).
- End-to-end: launches an in-process server via httpx's ASGI transport and runs the agent's token-injection hooks over it (no Ollama required — uses a fake LLM).