MCP server

Connect Shotoku to any MCP-compatible agent host — Claude, Cursor, Windsurf, or a custom agent — so that authorization happens automatically, mid-task, before your agent acts.

What is MCP?

MCP (Model Context Protocol) is an open standard that lets AI agents call external tools through a consistent interface. Instead of each agent needing custom integration code, MCP defines how tools are discovered, described, and called — so the same server works across Claude, Cursor, Windsurf, and any other MCP-compatible host.

When you add Shotoku as an MCP server, your agent gains direct access to its authorization tools. Before spending money, calling an API, or executing code, the agent can call authorize_action and let Shotoku make the call — automatically, inline, without you writing any integration glue.

The CLI is for you. The MCP server is for your agent.


Setup

1. Build the MCP server

From the repo root:

bash
pnpm build

This produces mcp/dist/index.js — the file your agent host will run.

2. Register Shotoku with your agent host

Most MCP hosts accept a JSON config file. Add a shotoku entry:

claude_desktop_config.json
{
  "mcpServers": {
    "shotoku": {
      "command": "node",
      "args": ["/absolute/path/to/shotoku/mcp/dist/index.js"],
      "env": {
        "SHOTOKU_POLICY": "/absolute/path/to/shotoku/policy.yaml",
        "SHOTOKU_LEDGER": "/absolute/path/to/shotoku/data/decisions.jsonl"
      }
    }
  }
}

Use absolute paths. Relative paths are resolved from the working directory where the server starts, which may differ by host.

For Claude Desktop, add this to ~/Library/Application Support/Claude/claude_desktop_config.json then restart the app.

3. Create the ledger directory

bash
mkdir -p data

Verifying the connection

In Claude Desktop, click the tools icon (hammer) in a new conversation and look for the shotoku section. You should see five tools:

  • authorize_action
  • get_decision
  • get_pending_approvals
  • approve_decision
  • deny_decision

The tools

authorize_action

The main gate. The agent calls this before performing any action — spending money, calling an API, running code, sending a message.

FieldRequiredWhat it means
actoryesWho is acting — the agent's name or ID
actionyesWhat the agent wants to do (purchase, api_call, execute_code, send_email, mcp_tool, custom)
resourceyesWhat it's acting on — a domain, service name, or endpoint
amountnoHow much it costs in USD, if applicable
contextnoAny extra details you want recorded with the decision (must be JSON-serializable, max 16 KB)
Example response
{
  "approved": true,
  "status": "approved",
  "reasons": [
    { "type": "policy_match", "text": "openai.com matched rule" },
    { "type": "limit_check",  "text": "Amount $5 within per-transaction limit of $50" },
    { "type": "budget_check", "text": "Daily budget remaining: $195" }
  ],
  "explanation": { "summary": "Request approved. All policy checks passed." },
  "decisionId": "dec_f3f0a6da3a69",
  "timestamp": "2026-06-22T15:02:00.000Z"
}

get_decision

Looks up a single past decision by ID. Returns the full record — original request, outcome, and reasons. Useful when the agent wants to reference what was decided earlier in a session.

get_pending_approvals

Returns every decision currently waiting for human review. The agent can surface this naturally: “You have 2 pending approvals — want to review them?” Returns an empty message when the queue is clear.

approve_decision

Approves a pending decision. Shotoku appends a new approval record to the ledger — it never mutates the original decision. Both the decision and the human approval are preserved in the audit trail.

json
{ "decisionId": "dec_abc123" }

deny_decision

Denies a pending decision. Same append-only behavior as approval.

json
{ "decisionId": "dec_abc123" }

Walkthrough

What a typical exchange looks like once Shotoku is connected:

bash
# You ask the agent something that involves an API call
You: Research the top three AI coding tools and summarize pricing.

# Agent calls authorize_action before doing anything
Agent → authorize_action({
  actor: "my-agent", action: "api_call",
  resource: "openai.com", amount: 0.05
})

# Shotoku checks policy, records decision, returns approved
Shotoku → { approved: true, status: "approved", decisionId: "dec_..." }

# Agent proceeds
Agent: Here are the top three AI coding tools...

If your policy requires human approval:

bash
Agent: I need to call a service that isn't on your allowlist.
       Shotoku flagged this as pending approval.

# From your terminal:
shotoku approve dec_abc123
# → ✓ Approved. Recorded as apr_xxx.

Environment variables

VariableDefaultWhat it controls
SHOTOKU_CONFIGshotoku.config.jsonPath to the Shotoku config file
SHOTOKU_POLICYpolicy.yamlPath to your policy file (overrides config)
SHOTOKU_LEDGERdata/decisions.jsonlPath to the local decision ledger (overrides config)

If SHOTOKU_POLICY or SHOTOKU_LEDGER are set they take precedence. Otherwise the server reads shotoku.config.json. If no config file exists it falls back to the defaults above.