MCP server
@warden/mcp is a Model Context Protocol server that lets a model pay from a Warden account. It runs on your machine over stdio and holds one agent key. The owner key never goes near it, and the contract refuses anything outside the agent's policy, so the worst a confused or manipulated model can do is spend what the policy allows.
Preview. @warden/mcp lives in the Warden repository under packages/ and is not on npm yet; build it from the workspace until it is. This page documents the interface in the repository. The contracts it wraps are live.
Before you start#
You need the Warden account address and an agent's private key. The owner registers the agent and its policy in the dashboard or with the SDK. Give the agent key a little Base Sepolia ETH for gas; the money it spends stays in the account.
Claude Code#
claude mcp add warden \
--env WARDEN_ACCOUNT=0xYourWardenAccount \
--env WARDEN_AGENT_KEY=0xAgentPrivateKey \
-- npx -y @warden/mcpCursor#
In .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project:
{
"mcpServers": {
"warden": {
"command": "npx",
"args": ["-y", "@warden/mcp"],
"env": {
"WARDEN_ACCOUNT": "0xYourWardenAccount",
"WARDEN_AGENT_KEY": "0xAgentPrivateKey"
}
}
}
}Configuration#
| Variable | Required | Default | Meaning |
|---|---|---|---|
WARDEN_ACCOUNT | yes | The Warden account the agent spends from. | |
WARDEN_AGENT_KEY | yes | The agent's private key, with or without 0x. Never the owner's. | |
WARDEN_RPC_URL | no | https://sepolia.base.org | JSON-RPC endpoint. |
WARDEN_CHAIN | no | base-sepolia | base-sepolia or 84532. |
The server prints one line to stderr when it starts. It never prints or returns the key.
Tools#
| Tool | Input | Returns | Moves money |
|---|---|---|---|
warden_balance | token? | Balance per token, the per-payment cap, window cap, spent and remaining this window, when it resets, the approval threshold, and the agent key's gas balance. | No |
warden_check | token, to, amount, or target, data?, value? | allowed, needs_approval, or denied with a reason and an explanation. Free. | No |
warden_pay | token, to, amount, memo? | executed with the transaction hash and a Basescan link, pending_approval with a requestId, or denied. | Yes |
warden_call | target, data or function with args, value?, memo? | The same statuses as warden_pay. function takes a signature such as transfer(address,uint256) and the server encodes args. | Yes |
warden_request_status | requestId, waitSeconds? | pending, approved with the transaction hash, rejected with the owner's reason, or expired. With waitSeconds (up to 300) it waits for the owner first. | No |
token is "USDC", "ETH" or a token address. Amounts are whole token units as decimal strings: "2.5" is 2.5 USDC. Every result comes back both as JSON text and as structuredContent.
Results the model sees#
| Result | When |
|---|---|
status: "denied", isError: true | The policy refused the payment or call. reason is one of the denial reasons. The server checks before sending, so a refusal costs no gas. |
decision: "denied" from warden_check | The same reasons, as an answer rather than an error. |
status: "pending_approval" | The amount is at or above the approval threshold. Nothing has moved. Follow up with warden_request_status. |
status: "error", isError: true | Bad input (a malformed address or amount, data and function together), an RPC failure, or a contract revert other than a denial. message says which. |
Use from code#
import { createWardenMcpServer } from "@warden/mcp";
const server = createWardenMcpServer({ agent, deployment }); // agent from createWardenAgent
await server.connect(transport);Setting a policy for a model#
A model is a good reason to keep caps tight:
- Allowlist recipients. With
anyRecipient, a prompt injection becomes a payment to the attacker's address, up to the cap. - Keep the per-payment cap at what one legitimate payment costs, and set an approval threshold below it for anything unusual.
- Use a short window. An hourly cap limits how much a runaway loop spends before you notice.
- Give the key only the ETH it needs for gas.