Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ Full schemas: [`/api/openapi.json`](https://getsanction.com/api/openapi.json).

The agent carries a Sanction wallet. Discovery: `GET /.well-known/wallet-card.json`.
stdio MCP is cooperative — the host must ask before acting.
Cursor plugin (MCP + skills): [`cursor-plugin/`](cursor-plugin/).

```json
{
Expand Down
34 changes: 34 additions & 0 deletions cursor-plugin/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "sanction",
"displayName": "Sanction",
"version": "0.1.0",
"description": "Independent authorization plane for AI agents — approve, escalate, or deny spend, tools, credentials, and provisioning. Wires the hosted wallet MCP; the host must ask before acting.",
"author": {
"name": "Sanction / getsanction.com (ericlovold)"
},
"homepage": "https://getsanction.com",
"repository": "https://github.com/ericlovold/sanction",
"license": "MIT",
"keywords": [
"sanction",
"mcp",
"authorization",
"agent-orchestration",
"wallet",
"spend",
"governance"
],
"category": "Agent Orchestration",
"logo": "assets/logo.svg",
"variables": {
"type": "object",
"properties": {
"SANCTION_AGENT_KEY": {
"type": "string",
"title": "Sanction agent key (secret)",
"description": "Secret. Agent API key from the Sanction console (pxy_…). Create a wallet, then an agent, and paste the key here. Never commit this value."
}
},
"required": ["SANCTION_AGENT_KEY"]
}
}
25 changes: 25 additions & 0 deletions cursor-plugin/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
MIT License

Copyright (c) 2026 Eric Lovold

This LICENSE applies only to the Cursor plugin package in this directory
(`cursor-plugin/`). The parent Sanction repository is licensed under the
Functional Source License 1.1 (FSL-1.1-MIT); see ../LICENSE.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
70 changes: 70 additions & 0 deletions cursor-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Sanction

Independent authorization plane for AI agents. Before spend, an MCP tool, a credential, a provision, or a new capability becomes irreversible, the agent asks Sanction — approve, escalate to a human, or deny.

This plugin is the authorization connector you add before AWS, Zoom, and the rest can spend or invoke. Neighbors on the marketplace: Ramp / Link (payments), Runlayer (MCP policy and audit). Category: **Agent Orchestration**.

For teams governing their own agents — budgets, tool policy, vaulted credentials — not only platforms embedding Sanction in a shipped product.

## Install

1. Add the plugin (Cursor Marketplace, or [local](#test-locally)).
2. Set `SANCTION_AGENT_KEY` under Plugins → Configure. Create a wallet and agent at [getsanction.com](https://getsanction.com); copy the `pxy_…` agent key (shown once). Never commit it.
3. Reload Cursor. The `sanction` MCP server should appear.

The plugin calls `https://getsanction.com/mcp` (Streamable HTTP) with `x-api-key`. Bearer also works on the hosted URL; this package leads with `x-api-key`.

## What the MCP exposes

Same ten tools as the hosted wallet. Do not invent others.

| Tool | What it does |
|------|----------------|
| `sanction_authorize` | Ask before purchase, subscribe, transfer, or API credit top-up. |
| `sanction_authorize_tool` | Ask before another MCP tool, shell, deploy, or email send. |
| `sanction_authorize_capability` | Ask before acquiring a new skill, plugin, integration, or API. |
| `sanction_authorize_provision` | Ask before provisioning seats, licenses, or infrastructure (resource + dollars). |
| `sanction_check_authorization` | Poll an escalated request for its one-use grant. |
| `sanction_wallet_status` | Today / MTD spend and token totals, plus pending approvals. |
| `sanction_request_execution` | Mint a short-lived mandate (JWT) for a child agent or counterparty. |
| `sanction_inject_credential` | Retrieve a vaulted secret under that mandate (audit-logged). |
| `sanction_log_tokens` | Record LLM token usage against the token budget. |
| `sanction_log_outcome` | Record a confirmed business outcome (feeds cost-per-outcome ceilings). |

## Skills included

| Skill | When to use |
|-------|-------------|
| `before-spend` | Before purchase, subscribe, transfer, or API credit top-up. |
| `before-tool` | Before another MCP tool, shell, deploy, or email send. |
| `wallet-status` | Start of long or expensive work, or after a budget error. |
| `handle-escalation` | When any `authorize*` call returns `escalated`. |

v1 is MCP + skills only (portable Agent Orchestration connector). No rules, agents, commands, or hooks.

## Honest limits

This plugin wires the **cooperative** hosted wallet. The host must ask before acting. It does not intercept every MCP `tools/call`. Skipping the ask is not a bypass the engine can see.

For intercepted `tools/call`, register the upstream and point the host at the broker (`/mcp/broker/<name>`), not this plugin's wallet URL. See [The agent wallet](https://github.com/ericlovold/sanction/blob/main/docs/AGENT-WALLET.md).

## License

MIT for this package (`cursor-plugin/`). The parent product is [FSL-1.1-MIT](https://github.com/ericlovold/sanction/blob/main/LICENSE).

## Submit notes

Interim home in [ericlovold/sanction](https://github.com/ericlovold/sanction). Extract later to a public MIT-only repo, then submit at [cursor.com/marketplace/publish](https://cursor.com/marketplace/publish) and [cursor.directory](https://cursor.directory). Marketplace category: **Agent Orchestration**.

## Test locally

Cursor loads plugins from `~/.cursor/plugins/local/` ([docs](https://cursor.com/docs/plugins)):

```bash
mkdir -p ~/.cursor/plugins/local
ln -s /path/to/sanction/cursor-plugin ~/.cursor/plugins/local/sanction
```

Reload Window (Developer: Reload Window). Set `SANCTION_AGENT_KEY` in Plugins → Configure. Confirm the `sanction` MCP server and the four skills in Customize.

If the plugin does not appear, copy the directory instead of symlinking. On Teams/Enterprise, local imports may be disabled under Marketplace and Plugins.
11 changes: 11 additions & 0 deletions cursor-plugin/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions cursor-plugin/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"mcpServers": {
"sanction": {
"url": "https://getsanction.com/mcp",
"headers": {
"x-api-key": "${SANCTION_AGENT_KEY}"
}
}
}
}
18 changes: 18 additions & 0 deletions cursor-plugin/skills/before-spend/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: before-spend
description: Use this before any purchase, subscription, money transfer, or API credit top-up. Call sanction_authorize first; never proceed when it returns false; if status is escalated, wait and follow handle-escalation.
---

# Before spend

The wallet is cooperative: Sanction does not intercept the payment. Call it before the spend becomes irreversible.

## Steps

1. Gather `action` (`purchase` | `subscribe` | `transfer`), `amount_usd`, `merchant`, and `category` (`software`, `services`, `research`, `infrastructure`, `marketing`, `legal`, or `other`). Add `description` when a human may have to approve.
2. Call `sanction_authorize` with those fields. Do not invent extra tools.
3. If `authorized` is true, proceed with the spend.
4. If `authorized` is false and status is not `escalated`, stop. Do not retry with a different amount or merchant to dodge policy.
5. If status is `escalated`, do not spend. Keep the original fields and follow **handle-escalation**. After approval, retry this exact `sanction_authorize` call with the returned `grant_id`.

Provisioning seats, licenses, or infrastructure is a different call: `sanction_authorize_provision` (resource + dollars in one request). Same rule: never provision on false; escalations go through handle-escalation.
18 changes: 18 additions & 0 deletions cursor-plugin/skills/before-tool/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: before-tool
description: Use this before invoking another MCP tool, a shell command, a deploy, or sending email. Call sanction_authorize_tool first; never invoke the target when it returns false.
---

# Before tool

The wallet is cooperative: this plugin does not intercept other MCP `tools/call`. Ask before you invoke.

## Steps

1. Name the exact tool about to run (`tool`, e.g. `github.create_deployment`, `shell.exec`, `email.send`). Include `server` and `arguments` when known — they surface on escalation.
2. Call `sanction_authorize_tool`. Do not invent extra tools.
3. If `authorized` is true, invoke the target once.
4. If `authorized` is false and status is not `escalated`, do not invoke.
5. If status is `escalated`, do not invoke. Follow **handle-escalation**, then retry this exact `sanction_authorize_tool` call with the returned `grant_id`.

Acquiring a new capability (installing a skill or plugin, enabling an integration, calling an API you have not used) is `sanction_authorize_capability`. Same rule: never acquire it on false.
18 changes: 18 additions & 0 deletions cursor-plugin/skills/handle-escalation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: handle-escalation
description: Use this when any sanction_authorize, sanction_authorize_tool, sanction_authorize_capability, or sanction_authorize_provision call returns status escalated. Poll sanction_check_authorization with request_id; on approved, retry the original call with grant_id; on denied, stop.
---

# Handle escalation

Escalation means a human must approve. Do not proceed, and do not start a different request for the same action.

## Steps

1. Keep the **original** authorize fields exactly (action/amount/merchant, tool name, capability, provision line, etc.) and the `request_id` from the escalated response.
2. Poll `sanction_check_authorization` with that `request_id`.
3. While status stays `escalated`, wait and poll again. Do not invoke, spend, provision, or acquire the capability.
4. When status is `approved` and a `grant_id` is returned, retry the **same** original tool (`sanction_authorize`, `sanction_authorize_tool`, `sanction_authorize_capability`, or `sanction_authorize_provision`) with identical fields plus that `grant_id`. A field mismatch is denied (`GRANT_MISMATCH`). The grant is one-use.
5. When status is denied (or the grant expires unused), stop. Do not proceed.

The wallet is cooperative: polling does not complete the action. Only a successful retry of the original authorize call does.
17 changes: 17 additions & 0 deletions cursor-plugin/skills/wallet-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: wallet-status
description: Use this at the start of long or expensive agentic work, or after a budget error from authorize or log_tokens. Call sanction_wallet_status to check spend and token headroom before continuing.
---

# Wallet status

The wallet is cooperative: status is a check, not a lock. Call it before expensive work so you stop on empty budget instead of discovering it mid-flight.

## Steps

1. Call `sanction_wallet_status` (no arguments). The wallet is derived from the agent key — do not hardcode a wallet id.
2. Read today's and month-to-date spend and token totals, plus pending approvals.
3. If a horizon is exhausted, stop expensive LLM calls and spend. Notify the owner; do not proceed hoping the next call will pass.
4. After a budget error from `sanction_authorize`, `sanction_authorize_provision`, or `sanction_log_tokens`, call this again, then stop or wait — do not retry the expensive action until headroom exists.

Prefer the Sanction LLM gateway for model calls when available; `sanction_log_tokens` is the honest client-side meter when you cannot.
Loading