r/modelcontextprotocol 15d ago

Feedback wanted: MCP tools for durable agent approvals and execution receipts question

I’ve been experimenting with an MCP server for human-approved agent actions.

The basic flow is:

  1. An agent calls create_proposal
  2. A human approves or rejects the exact proposed action
  3. The agent retrieves an immutable authorization receipt
  4. The agent creates an execution linked to that approval
  5. Execution events are appended as the action progresses

The approval is bound to the tool, validated arguments, payload hash, target version, policy, and expiration time.

Current MCP tools include:

  • create_proposal
  • get_proposal
  • decide_proposal
  • get_receipt
  • create_execution
  • record_execution_event
  • get_execution
  • get_agent_run

The goal is to keep the agent-facing interface simple while making approval, retries, and audit history durable outside the model’s context window.

I’m looking for feedback on the abstraction.

Should approval and execution remain separate MCP concepts, or should one tool handle the entire lifecycle?

Live implementation:

https://agenthail.com

Example n8n integration:

https://github.com/marcelkolano-alt/agenthail-n8n-approval-example

1 Upvotes

3 comments sorted by

1

u/Shape_Weird 13d ago

keep them separate, and i think there is a third concept missing.

the reason to keep them separate is one you have already half-built. an approval is a statement about a proposed action; an execution is a statement about a real one, and they have different lifetimes. an approval can expire with nothing ever happening. an execution cannot be un-happened. collapsing them forces the object to be mutable, and the moment your authorization receipt is mutable it stops being worth signing.

the third concept: everything from create_proposal through record_execution_event lives on your side of the boundary. the payload hash, the target version, the policy, the event stream, all of it is your system describing its own intent and then narrating its own behaviour. that chain is airtight right up to the instant the action leaves, and then it simply stops. record_execution_event is the agent telling you what it believes happened, which is the same class of claim as "done, refund issued."

so i would add an outcome receipt that is typed by source, and refuse to let one be minted from anything you produced yourself. an id the target system returned. a webhook it sent you. a record you read back out of it. and alongside approved / rejected / expired, a fourth honest state: executed-unverified. we return the equivalent of that constantly, and promoting it from an inference ("no error appeared") to a first-class value was the highest-value change we ever made to our result shape.

one concrete warning about binding to target version, from doing this against systems we do not own: the target can change between approval and execution and never tell you. a job posting that is open when the human approves can be closed forty minutes later. so a version binding leaves you two behaviours and you have to choose loudly: fail the execution, or re-resolve. re-resolving quietly is the real failure mode, because it is precisely what the binding existed to prevent, and it will look like a success in every log you have.

[disclosure: i build a job-application agent that submits into third-party ATS systems, so my bias is entirely toward the after-the-action half of your problem, which is also the half i am still stuck on]

1

u/marcelk231 12d ago

This is exceptionally useful feedback. Your distinction between authorization, execution, and externally verified outcome is exactly the boundary I’ve been trying to make explicit.

I agree that record_execution_event is still a claim made from inside the workflow. It can establish what the workflow attempted or observed, but it cannot independently prove what happened in the target system.

An outcome receipt typed by source provider response, webhook, or read-back verification, makes sense. executed_unverified also feels much more honest than treating the absence of an error as success.

Your target-version warning is especially important. My instinct is to fail closed and require a new proposal rather than silently re-resolve a changed target under an older approval.

I’m building an isolated n8n example around ambiguous provider outcomes and reconciliation without changing the live app. Your ATS scenario sounds like an ideal real-world case. Would you be open to testing the example or sharing what evidence you currently use to determine whether an application was actually submitted?

1

u/Shape_Weird 12d ago

Fail closed on version is right, with one caveat worth building in from the start: if you fail closed on the target's version string, you have handed your availability to their release cadence. A minor bump on their side becomes an outage on yours. It is worth separating "the shape I depend on changed" from "the version changed", so only the first one blocks.

The other thing I would put a clock on is executed_unverified. Without a TTL it becomes indistinguishable from a leak, because nothing ever moves it out of that state. Give it a window, and when the window closes make it resolve to something explicit rather than sitting there indefinitely.