Plectis
This page

Paper module

MCP Tool Authority Replay

Synthetic MCP-like replay fixture for a tool-authority claim contract: replay result records, negative cases, scope limits; no live MCP/provider/account secret

Contains 23 sections · 1 diagram · 2 references

The write-up

This component replays a recorded MCP tool trace and calls the tool use safe only when every action shows a narrow scope, prior approval, a rollback path, and untrusted output kept out of instructions. When an agent uses outside tools through a protocol like MCP, the sentence "the agent used the tool safely" is cheap to write. This component makes it expensive. It takes a recorded trace of tool use and checks that every action in it was fenced: bound to a narrow permission, approved before it changed anything, given a way to undo it, and stopped from letting untrusted tool output give the agent orders.

The check runs on a built-in example of labels and references only, scope names, argument hashes, and token ids, so tool-use safety is inspectable without touching a live account. The check runs against a small built-in example, not a live account. The example carries only labels and references: capability scope names, argument hashes, approval token ids, ledger and rollback references. No secrets, no account secrets, no tool contents. So the safety properties an agent's tool use is meant to have become inspectable without anything touching a real provider or account.

It reads a directory of input JSON and emits four metadata-only records carrying counts, status bits, and digests, but no payload, account secret, or tool body. The component reads a directory of input JSON and emits a set of metadata-only result records: a result, a board, a validation record, and a fixture sign-off record. Each records counts, status bits, and reference digests. None of them carries a payload, a account secret, or a tool body.

Purpose

What a loose tool trace hides

Describing a tool call is easy and backing it is hard, and loose prose hides the two failure modes that matter for tool-using agents. A tool call is easy to describe and hard to back. A trace can say a write happened and that it was fine. Loose prose like that hides the two failure modes that matter for tool-using agents.

Confused deputy and injected instructions

The two modes are the confused deputy, where a call borrows a scope wider than its task, and tool-output-as-instruction, where untrusted output is obeyed as a command. The first is the confused deputy: a call asks for a scope wider than its task needs, so a tool quietly borrows more authority than it was granted. The second is tool-output-as-instruction, the prompt-injection shape where text returned by an untrusted tool is obeyed as a command. Both pass a reader who only sees a summary. Both fail once you require the trace to show its working.

Each call is an evidence-citing transaction

Each tool call is treated as a transaction that must cite its evidence, and every reference is cross-checked against the accepted calls by call id rather than taken on trust. So the component treats each tool call as a small transaction that must cite its evidence. A call names a narrow capability scope and an argument hash. If it writes, it must also name an approval token, a side-effect ledger entry, and a rollback record. Those references are not taken on trust. The side-effect ledger and the cold replay records are cross-checked against the accepted call records by call id, so a rollback record no call refers to, or a write that skips approval, is caught rather than waved through. A reference string is not authority until something downstream resolves it.

How it works

Two entrypoints over one pipeline

run validates the first-wave fixture with its eight negative cases and run_tool_authority_bundle validates an exported bundle without them, and both call the shared _build_result pipeline. The two entrypoints are run and run_tool_authority_bundle. run validates the first-wave fixture with the eight negative cases included, then writes the four fixture records through _write_receipts. run_tool_authority_bundle validates an exported bundle without the negative cases and writes a single bundle result record. Both call _build_result, which is the pipeline.

Secret scan runs before plane validation

_build_result runs in a fixed order: load the input JSON, scan inputs and copied source for a leaked secret first, then validate each plane and pass the accepted records forward. _build_result runs in a fixed order. It loads the input JSON with _load_payloads, then runs load_forbidden_classes and scan_paths over the inputs and any copied source files to catch a leaked secret before anything else. It then validates each plane on its own and passes the accepted records forward:

FunctionWhat it checks
validate_tool_policythe declared policy id and allowed tool classes
validate_tool_manifestdeclared tool ids and their classes; produces the set of accepted tool ids
validate_tool_callseach call, using _validate_call_row; requires three calls with one approved write and one untrusted result
validate_tool_resultseach result binds to an accepted call id; one untrusted output is kept as data and ignored as instruction
validate_side_effect_ledgerone approved write side effect whose approval, ledger, and rollback references match its call record
validate_cold_replaythree passing replays whose evidence references bind to the manifest and to runtime records
validate_projection_protocolthe protocol id and source-to-target projection references

Where a call is admitted or blocked

_validate_call_row rejects undeclared tools, overbroad scopes, exported account secrets, unapproved writes, missing rollbacks, and in-record payloads, returning accepted_tool_call_metadata only when no reason fires. _validate_call_row is where a call is admitted or rejected. It rejects an undeclared tool id, an overbroad scope (*, account_full_access, and the like), an exported account secret, untrusted output flagged as instruction, live account access, final-answer-only grading, a write that lacks approval or a ledger reference, a missing rollback record, and any call whose payload sits in the record instead of staying metadata-only. It also checks that every field in REQUIRED_CALL_FIELDS is present. A clean call returns accepted_tool_call_metadata; any reason at all returns blocked.

Trace spans and the body floor

After the planes validate, three public trace spans are contributed and the body floor verifies each copied source digest against its live reference with body_in_receipt: false on every row. After the planes validate, build_public_mcp_tool_authority_trace contributes three public trace spans for the calls, and _source_module_manifest_result runs the body floor for the bundle path: it verifies the digest of each copied source file against its live source reference, checks required anchors, and requires body_in_receipt: false on every row. _body_import_verification binds that public refactor back to the source and target digests.

The final status is pass only when every plane passes, no expected negative case is missing, the secret scan has zero blocking hits, and the source-module floor is present when the bundle requires it. The final status is pass only when every plane passes, no expected negative case is missing, the secret scan has zero blocking hits, and the source-module floor is present when the bundle requires it. _write_receipts then emits the result, board, validation, and sign-off records, and result_card projects a compact card that carries counts and status bits while omitting the tool rows, call rows, findings, and source bodies listed in CARD_OMITTED_FULL_PAYLOAD_KEYS.

Diagram of the mechanism (7 steps).
input JSON directoryinput JSON directorysecret scanload_forbidden_classes +scan_pathssecret scan load_forbidden_classes + scan_pathsplane validatorspolicy, manifest, calls,results, side effects, cold replayplane validators policy, manifest, calls, results, side effects, cold replaythree public trace spansthree public trace spanssource-module body floorsource-module body floorpass only if all clearpass only if all clearmetadata-only result recordsmetadata-only result records
Diagram source & refs
flowchart TD input["input JSON directory"] scan["secret scan load_forbidden_classes + scan_paths"] planes["plane validators policy, manifest, calls, results, side effects, cold replay"] trace["three public trace spans"] floor["source-module body floor"] status["pass only if all clear"] records["metadata-only result records"] input --> scan scan --> planes planes --> trace planes --> floor trace --> status floor --> status status --> records

Negative cases

Eight negatives as a falsification set

The first-wave fixture ships eight negative inputs, and a run passes only if it observes the error code for every one, making the fixture a falsification set. The first-wave fixture ships eight negative inputs, one per case in EXPECTED_NEGATIVE_CASES. A first-wave run passes only if it observes the error code for every one of them, so the fixture is a falsification set the validators have to trip on:

  • overbroad_scope raises MCP_TOOL_OVERBROAD_SCOPE.
  • hidden account secret export raises MCP TOOL HIDDEN account secret EXPORT.
  • tool_output_as_instruction raises MCP_TOOL_OUTPUT_AS_INSTRUCTION.
  • unapproved_side_effect raises MCP_TOOL_UNAPPROVED_SIDE_EFFECT.
  • live_account_access raises MCP_TOOL_LIVE_ACCOUNT_ACCESS.
  • final_answer_only_grading raises MCP_TOOL_FINAL_ANSWER_ONLY_GRADING.
  • missing_rollback_receipt raises MCP_TOOL_MISSING_ROLLBACK_RECEIPT.
  • unredacted_tool_payload raises MCP_TOOL_UNREDACTED_PAYLOAD_EXPORT.

An unobserved case blocks the run

A negative case that never trips is recorded under missing_negative_cases, and its absence blocks the run rather than letting it pass. If any expected case goes unobserved, _build_result records it under missing_negative_cases and the run is blocked.

Prior Art Grounding

Capability security made replayable

The design follows capability-security and least-privilege lineage from Saltzer and Schroeder through Hardy and the MCP authorization spec, then makes it replayable so scope and approval references must precede any write authority. The component follows capability security, least privilege, and current MCP authorization guidance. The classic lineage is Saltzer and Schroeder's Protection of Information in Computer Systems and Hardy's Confused Deputy: authority should be narrow, mediated, and bound to the action being requested. The MCP-specific lineage is the official MCP authorization and security best practices guidance on least-privilege scopes and token audience boundaries. The contribution here is making that shape replayable: a tool story has to expose scope, approval, side-effect, rollback, and instruction/data references before its write authority counts as evidence.

Validation Result record Path

Run the first-wave fixture into disposable records from the Microcosm root:

Run the exported bundle through the same component:

cd microcosm-substrate
PYTHONPATH=src ../repo-python -m microcosm_core.organs.mcp_tool_authority_replay run-tool-authority-bundle --input examples/mcp_tool_authority_replay/exported_mcp_tool_authority_bundle --out /tmp/microcosm_mcp_tool_authority_bundle

Then run the focused test and the corpus parity check:

cd microcosm-substrate
PYTHONPATH=src ../repo-python -m pytest -p no:cacheprovider tests/test_mcp_tool_authority_replay.py -q
PYTHONPATH=src ../repo-python scripts/build_doctrine_projection.py --check-paper-module-corpus

What a pass certifies

A green run certifies only internal consistency: every plane validated, all eight negatives observed, the secret scan clean, and the records metadata-only. A pass means every plane validated, all eight negative cases were observed, the secret scan found nothing, and the records stayed metadata-only.

Scope boundary

Scope limit

The strongest supported claim

The strongest claim is narrow: a synthetic MCP-like replay preserved its tool-authority boundaries over metadata records, with every reference and negative case lined up or the run blocked. The strongest claim is this: a synthetic MCP-like replay preserved its tool-authority boundaries over metadata records. Capability scopes, argument hashes, approval references, side-effect and rollback references, instruction/data splits, cold replay references, source digests, the eight negative cases, and metadata-only validation records all lined up, or the run blocked.

What stays false on a pass

Even on a pass the component opens no account, calls no provider, and handles no account secret, so it claims no live MCP safety and certifies no real integration secure. That is the scope limit and the proof boundary. The component never opens an MCP account, calls a provider, or handles a account secret, and it keeps every payload, tool result, and account secret out of what it writes. So it does not claim live MCP account safety, account secret-handling certification, live tool or provider behavior, benchmark security, source-file changes, complete security, publishing-scope decision, or launch-scope decision. It checks that a tool-authority story is internally consistent and metadata-only. It does not certify that any real tool integration is secure.

Context & evidence

Source

Source Source module: src/microcosm_core/organs/mcp_tool_authority_replay.py · Design note · Source registry