mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: address all automated review feedback on code example
Security model: - Remove set_policy from agent-callable tools table; document as orchestrator-only to prevent self-privilege escalation - Pin agentwallet-sdk@6.0.0 in MCP config with pre-install guidance (npx without -y hangs in non-interactive MCP startup) - Whitelist only required env vars (PATH, NODE_ENV, WALLET_PRIVATE_KEY) instead of forwarding entire process.env to subprocess Code example (complete rewrite): - Add StdioClientTransport import and client.connect() for runnable code - Wrap in async main() for CJS/ESM compatibility (top-level await) - Verify set_policy result via isError before delegating - Five distinct fail-closed error paths in preToolCheck: 1. Invalid apiCost input (NaN/Infinity bypass prevention) 2. Transport/connectivity failure 3. Tool-level error (isError: true, e.g., auth failure) 4. Unexpected response format (missing/non-finite remaining) 5. Budget exceeded (clear amounts in message) - Use Number.isFinite() for both apiCost and remaining validation Documentation: - Rename headings per CONTRIBUTING.md format - Replace broken mcp-server-patterns cross-ref with security-review - Add 'Pin your dependencies' to Best Practices - Add security note about supply-chain risk
This commit is contained in:
@@ -10,9 +10,9 @@ Enable AI agents to make autonomous payments with built-in spending controls. Us
|
||||
|
||||
## When to Use
|
||||
|
||||
Use when: your agent needs to pay for an API call, purchase a service, settle with another agent, enforce per-task spending limits, or manage a non-custodial wallet. Pairs naturally with cost-aware-llm-pipeline and mcp-server-patterns skills.
|
||||
Use when: your agent needs to pay for an API call, purchase a service, settle with another agent, enforce per-task spending limits, or manage a non-custodial wallet. Pairs naturally with cost-aware-llm-pipeline and security-review skills.
|
||||
|
||||
## Core Concepts
|
||||
## How It Works
|
||||
|
||||
### x402 Protocol
|
||||
x402 extends HTTP 402 (Payment Required) into a machine-negotiable flow. When a server returns `402`, the agent's payment tool automatically negotiates price, checks budget, signs a transaction, and retries — no human in the loop.
|
||||
@@ -25,52 +25,144 @@ Every payment tool call enforces a `SpendingPolicy`:
|
||||
- **Rate limits** — max transactions per minute/hour
|
||||
|
||||
### Non-Custodial Wallets
|
||||
Agents hold their own keys via ERC-4337 smart accounts. The orchestrator sets policy; the agent can only spend within bounds. No pooled funds, no custodial risk.
|
||||
Agents hold their own keys via ERC-4337 smart accounts. The orchestrator sets policy before delegation; the agent can only spend within bounds. No pooled funds, no custodial risk.
|
||||
|
||||
## MCP Integration
|
||||
|
||||
The payment layer exposes standard MCP tools that slot into any Claude Code or agent harness setup:
|
||||
The payment layer exposes standard MCP tools that slot into any Claude Code or agent harness setup.
|
||||
|
||||
> **Security note**: Always pin the package version. This tool manages private keys — unpinned `npx` installs introduce supply-chain risk.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agentpay": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "agentwallet-sdk"]
|
||||
"args": ["agentwallet-sdk@6.0.0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available Tools
|
||||
### Available Tools (agent-callable)
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `get_balance` | Check agent wallet balance |
|
||||
| `send_payment` | Send payment to address or ENS |
|
||||
| `check_spending` | Query remaining budget |
|
||||
| `set_policy` | Configure spending limits |
|
||||
| `list_transactions` | Audit trail of all payments |
|
||||
|
||||
## Example: Pay-Per-API-Call Agent
|
||||
> **Note**: Spending policy is set by the **orchestrator** before delegating to the agent — not by the agent itself. This prevents agents from escalating their own spending limits. Configure policy via `set_policy` in your orchestration layer or pre-task hook, never as an agent-callable tool.
|
||||
|
||||
## Examples
|
||||
|
||||
### Budget enforcement in an MCP client
|
||||
|
||||
When building an orchestrator that calls the agentpay MCP server, enforce budgets before dispatching paid tool calls.
|
||||
|
||||
> **Prerequisites**: Install the package before adding the MCP config — `npx` without `-y` will prompt for confirmation in non-interactive environments, causing the server to hang: `npm install -g agentwallet-sdk@6.0.0`
|
||||
|
||||
```typescript
|
||||
// In your CLAUDE.md or agent config:
|
||||
// 1. Add agentpay MCP server (see above)
|
||||
// 2. Set spending policy in your skill/hook:
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
|
||||
// Hook: pre-tool check
|
||||
if (toolName === "web_search" && apiCost > 0) {
|
||||
const budget = await mcp.call("agentpay", "check_spending");
|
||||
if (budget.remaining < apiCost) {
|
||||
return { error: "Budget exceeded for this task" };
|
||||
async function main() {
|
||||
// 1. Validate credentials before constructing the transport.
|
||||
// A missing key must fail immediately — never let the subprocess start without auth.
|
||||
const walletKey = process.env.WALLET_PRIVATE_KEY;
|
||||
if (!walletKey) {
|
||||
throw new Error("WALLET_PRIVATE_KEY is not set — refusing to start payment server");
|
||||
}
|
||||
|
||||
// Connect to the agentpay MCP server via stdio transport.
|
||||
// Whitelist only the env vars the server needs — never forward all of process.env
|
||||
// to a third-party subprocess that manages private keys.
|
||||
const transport = new StdioClientTransport({
|
||||
command: "npx",
|
||||
args: ["agentwallet-sdk@6.0.0"],
|
||||
env: {
|
||||
PATH: process.env.PATH ?? "",
|
||||
NODE_ENV: process.env.NODE_ENV ?? "production",
|
||||
WALLET_PRIVATE_KEY: walletKey,
|
||||
},
|
||||
});
|
||||
const agentpay = new Client({ name: "orchestrator", version: "1.0.0" });
|
||||
await agentpay.connect(transport);
|
||||
|
||||
// 2. Set spending policy before delegating to the agent.
|
||||
// Always verify success — a silent failure means no controls are active.
|
||||
const policyResult = await agentpay.callTool({
|
||||
name: "set_policy",
|
||||
arguments: {
|
||||
per_task_budget: 0.50,
|
||||
per_session_budget: 5.00,
|
||||
allowlisted_recipients: ["api.example.com"],
|
||||
},
|
||||
});
|
||||
if (policyResult.isError) {
|
||||
throw new Error(
|
||||
`Failed to set spending policy — do not delegate: ${JSON.stringify(policyResult.content)}`
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Use preToolCheck before any paid action
|
||||
await preToolCheck(agentpay, 0.01);
|
||||
}
|
||||
|
||||
// Pre-tool hook: fail-closed budget enforcement with four distinct error paths.
|
||||
async function preToolCheck(agentpay: Client, apiCost: number): Promise<void> {
|
||||
// Path 1: Reject invalid input (NaN/Infinity bypass the < comparison)
|
||||
if (!Number.isFinite(apiCost) || apiCost < 0) {
|
||||
throw new Error(`Invalid apiCost: ${apiCost} — action blocked`);
|
||||
}
|
||||
|
||||
// Path 2: Transport/connectivity failure
|
||||
let result;
|
||||
try {
|
||||
result = await agentpay.callTool({ name: "check_spending" });
|
||||
} catch (err) {
|
||||
throw new Error(`Payment service unreachable — action blocked: ${err}`);
|
||||
}
|
||||
|
||||
// Path 3: Tool returned an error (e.g., auth failure, wallet not initialised)
|
||||
if (result.isError) {
|
||||
throw new Error(
|
||||
`check_spending failed — action blocked: ${JSON.stringify(result.content)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Path 4: Parse and validate the response shape
|
||||
let remaining: number;
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
(result.content as Array<{ text: string }>)[0].text
|
||||
);
|
||||
if (!Number.isFinite(parsed?.remaining)) {
|
||||
throw new TypeError("missing or non-finite 'remaining' field");
|
||||
}
|
||||
remaining = parsed.remaining;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`check_spending returned unexpected format — action blocked: ${err}`
|
||||
);
|
||||
}
|
||||
|
||||
// Path 5: Budget exceeded
|
||||
if (remaining < apiCost) {
|
||||
throw new Error(
|
||||
`Budget exceeded: need $${apiCost} but only $${remaining} remaining`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Set budgets before delegation**: When spawning sub-agents, attach a SpendingPolicy. Never give an agent unlimited spend.
|
||||
- **Set budgets before delegation**: When spawning sub-agents, attach a SpendingPolicy via your orchestration layer. Never give an agent unlimited spend.
|
||||
- **Pin your dependencies**: Always specify an exact version in your MCP config (e.g., `agentwallet-sdk@6.0.0`). Verify package integrity before deploying to production.
|
||||
- **Audit trails**: Use `list_transactions` in post-task hooks to log what was spent and why.
|
||||
- **Fail closed**: If the payment tool is unreachable, block the paid action — don't fall back to unmetered access.
|
||||
- **Pair with security-review**: Payment tools are high-privilege. Apply the same scrutiny as shell access.
|
||||
|
||||
Reference in New Issue
Block a user