DidWork for Cursor
The agent edits, runs the command, reads the output and reports the task
complete. Everything it knows comes from its own execution — so a
deploy that exits 0 against a service that
never came up reads exactly like one that worked, and a refund call that
returns 200 reads exactly like a refund that
settled.
DidWork gives the agent a source that isn't itself: it forms a claim, DidWork asks the system that can prove it, and the agent gates on the verdict.
Install
One command, in your project:
npx @didwork/inspect init
It writes .cursor/mcp.json so the agent has
the did_verify tool, and
.cursor/rules/verify-outcomes.mdc so it
reaches for it — the rule being the part that actually changes
behaviour. Existing config is merged rather than replaced, running it
twice changes nothing, and
--dry-run shows the plan first.
Then reload the Cursor window so the MCP server starts.
Your key is never written into those files. They are project files you
commit, so the config references
DIDWORK_API_KEY and the key itself lives
outside the repo:
mkdir -p ~/.didwork printf '%s ' 'dk_your_key' > ~/.didwork/api_key chmod 600 ~/.didwork/api_key
The key file works where an environment variable does not: an editor
launched from the Dock or Start menu never sees what your shell
exports. A free key is at didwork.sh/console;
without one, http.ok still verifies
keylessly and nothing is stored.
Or configure it by hand
Add the MCP server to .cursor/mcp.json in
your project (or ~/.cursor/mcp.json for every
project), then reload the window:
{
"mcpServers": {
"didwork": {
"command": "npx",
"args": ["-y", "@didwork/mcp"],
"env": { "DIDWORK_API_KEY": "dk_your_key" }
}
}
}
Creating a key at didwork.sh/console also gives you a one-click Add to Cursor link.
There is also plugin
packaging carrying the same rule plus
/demo, /setup and
/verify commands. It is waiting on the Cursor
marketplace, so init above is the supported
install today — and it writes the same rule the plugin would.
See it catch a miss
Two fixture endpoints exist for exactly this. One is permanently
healthy; one permanently answers 503. Ask
the agent to verify the broken one — or run it yourself, with no
key and no account:
curl -s https://api.didwork.sh/v1/verify -H 'content-type: application/json' -d '{"type":"http.ok","expected":{"url":"https://didwork.sh/demo/broken"}}'
The verdict is FAILED with
STATUS_MISMATCH and
GET … → 503 as the evidence.
Swap /demo/broken for
/demo/healthy and the same check returns
VERIFIED. That contrast is the product: the
same check, two honest answers, neither self-reported.
What the plugin adds
| Piece | What it does |
|---|---|
| MCP server | did_verify, did_get, did_list, did_watch, did_watches, did_unwatch, did_usage, did_inspect_tool |
| Rule | rules/verify-outcomes.mdc — verify external side effects before reporting success |
| Skill | The claim types and the verify → gate workflow |
| Commands | /demo, /setup, /verify (plugin only) |
Cursor has no Stop-hook equivalent, so the rule does the work here. On Claude Code a Stop gate also blocks ending a turn when a side effect went unverified.
The rule
verify-outcomes.mdc is always applied. In
substance it asks the agent for three things: verify each externally
visible outcome before calling it done, gate on the verdict rather than
on its own output, and never read UNKNOWN as
success — poll or escalate instead.
That last one matters more than it looks. "I could not establish this" and "this is true" are the two states most reporting collapses into one.
Point it at your project
Ask the agent to look at what the repo actually touches —
dependency manifests, .env.example variable
names, CI workflows, a deployed URL — and map that to claim types.
Stripe in the dependencies means
stripe.refund and
stripe.subscription_active are available to
it; a GitHub remote means
github.pr_merged and
github.workflow_passed are.
Then connect those providers at
didwork.sh/console/providers with
read-only credentials. With the plugin installed,
/setup does this walk for you and backfills
verdicts over your recent merged PRs and CI runs.
What it can verify
Nearly forty claim types across Stripe, GitHub, GitLab, Linear, Jira, Sentry, Slack, email, and any public URL — the full table is in the docs. What each one refuses to call success is the useful part:
| Claim | What it refuses to call success |
|---|---|
github.pr_merged | closed but not merged |
stripe.refund | pending, wrong amount, or a duplicate |
linear.issue_completed | cancelled — which is not resolved |
github.release_published | still a draft |
http.ok | any status you did not expect |
The rule, if you skipped init
The MCP tools alone change nothing — an agent with a verifier it
never reaches for still grades its own work.
init writes this for you; if you configured
by hand, put it in .cursorrules or an
always-applied rule under
.cursor/rules/:
Before reporting a task complete, verify each outcome you claim with did_verify. Report done only on VERIFIED. Surface FAILED and UNKNOWN instead of retrying silently.
Troubleshooting
No did_verify tool. Reload
the Cursor window — MCP servers start with it. Check the MCP panel
in settings for a startup error.
Everything is UNKNOWN. The
claim named a system DidWork has no read credential for. Connect it at
didwork.sh/console/providers.
The agent verifies once and then stops bothering. The rule is advice, not enforcement. If you want something that refuses to let an unverified side effect pass, that is the Stop gate on Claude Code, or the merge gate in CI.
Next
Make it a merge requirement with DidWork for GitHub Actions, or wire another host yourself with the MCP server.