Paybond integrates with the Claude Agent SDK by wrapping paid custom tools registered via tool() and bundling them into an in-process MCP server.
SDK note: The Claude Agent SDK continues to evolve on npm and PyPI. Paybond tracks the
tool()andcreateSdkMcpServersurface with regression tests; pin SDK versions in production and runpaybond agent demo claude-agents smokein CI after upgrades.
Built-in SDK tools (unguarded)
Claude Agent SDK built-in tools (Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch, …) execute inside Anthropic's runtime and do not pass through Paybond spend verify, approval holds, or auto-evidence. Only tools you register with tool() and expose via the Paybond MCP server are governed.
Recommended: restrict query() allowedTools to your Paybond MCP entries (mcp__paybond__*) and omit built-ins for spend-sensitive agents.
import { createPaybondClaudeAgentsConfig, findUnguardedClaudeBuiltinTools, } from "@paybond/kit/claude-agents"; const { mcpServer, allowedTools } = createPaybondClaudeAgentsConfig(run, sdkTools, { queryAllowedTools: allowedToolsForQuery, // optional: emits a one-time console warning if built-ins remain }); const unguarded = findUnguardedClaudeBuiltinTools(allowedToolsForQuery); if (unguarded.length > 0) { // fail closed in production if needed }
For local development, createPaybondClaudeAgentsConfig warns once when queryAllowedTools includes known built-in names (disable with warnOnUnguardedBuiltins: false).
Install
Install
npm install @paybond/claude-agents @anthropic-ai/claude-agent-sdkImport from @paybond/claude-agents
import { createPaybondClaudeAgentsConfig } from "@paybond/claude-agents";- Equivalent subpath on the core package: `@paybond/kit/claude-agents` — use `@paybond/kit` when you need multiple adapters in one app.
- The Python extra installs **`claude-agent-sdk`** on PyPI (not `@anthropic-ai/claude-agent-sdk`). Wheels bundle the Claude Code CLI binary (~65MB) — plan container image size accordingly.
- Python: `paybond agent demo claude-agents smoke` requires the optional `claude-agents` extra. Use `pip install "paybond-kit[claude-agents]"`, `pipx install 'paybond-kit[claude-agents]'`, or `pipx inject paybond-kit claude-agent-sdk` (when base paybond-kit is already installed).
Recommended wiring
One-liner (sandbox): paybond.instrument({ policy, framework: "claude-agents", tools, sandbox: true }) or paybond.agent({ policy, framework: "claude-agents", tools }) returns claudeAgentsConfig with mcpServer and allowedTools for query().
TypeScript
import { tool } from "@anthropic-ai/claude-agent-sdk"; import { z } from "zod"; import { Paybond } from "@paybond/kit"; const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! }); const sdkTools = [ tool( "travel.book_hotel", "Book a hotel room", { estimatedPriceCents: z.number() }, async (args) => ({ content: [{ type: "text", text: JSON.stringify(await bookHotel(args)) }], structuredContent: await bookHotel(args), }), ), ]; const { claudeAgentsConfig } = await paybond.instrument({ policy: "./paybond.policy.yaml", // or preset id "travel" framework: "claude-agents", tools: sdkTools, }); const { mcpServer, allowedTools } = claudeAgentsConfig!; // query({ prompt, options: { mcpServers: { paybond: mcpServer }, allowedTools } })
Python
from claude_agent_sdk import tool from paybond_kit import Paybond paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"]) sdk_tools = [ tool("travel.book_hotel", "Book a hotel", {"estimated_price_cents": int}, book_hotel), ] result = await paybond.instrument( policy="./paybond.policy.yaml", framework="claude-agents", tools=sdk_tools, ) config = result.claude_agents_config # query(..., options={"mcp_servers": {"paybond": config.mcp_server}, "allowed_tools": config.allowed_tools})
Already bound a run? paybond.wrapTools(run, tools, { framework: "claude-agents" }) / paybond.wrap_tools(run, tools, framework="claude-agents").
See Agent middleware for run binding and tenant isolation.
Advanced / manual wiring
When you need step-by-step control over registry and bind:
- Bind a
PaybondAgentRunwith a tool registry. - Define side-effecting tools with the SDK
tool()helper. - Call
createPaybondClaudeAgentsConfig(run, tools)and passmcpServerandallowedToolstoquery().
TypeScript
import { tool } from "@anthropic-ai/claude-agent-sdk"; import { z } from "zod"; import { Paybond, createPaybondToolRegistry } from "@paybond/kit"; import { createPaybondClaudeAgentsConfig } from "@paybond/kit/claude-agents"; const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! }); const registry = createPaybondToolRegistry({ defaultDeny: true, sideEffecting: { "travel.book_hotel": { spendCents: (args: { estimatedPriceCents: number }) => args.estimatedPriceCents, evidencePreset: "cost_and_completion", }, }, }); const run = await paybond.agentRun.bind({ bootstrap: { kind: "sandbox", operation: "travel.book_hotel", requestedSpendCents: 20_000, completionPreset: "cost_and_completion", }, registry, }); const sdkTools = [ tool( "travel.book_hotel", "Book a hotel room", { estimatedPriceCents: z.number() }, async (args) => ({ content: [{ type: "text", text: JSON.stringify(await bookHotel(args)) }], structuredContent: await bookHotel(args), }), ), ]; const { mcpServer, allowedTools } = createPaybondClaudeAgentsConfig(run, sdkTools); // query({ prompt, options: { mcpServers: { paybond: mcpServer }, allowedTools } })
Python
from claude_agent_sdk import tool from paybond_kit import Paybond from paybond_kit.agent import create_paybond_tool_registry from paybond_kit.claude_agents import create_paybond_claude_agents_config paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"]) registry = create_paybond_tool_registry( { "default_deny": True, "side_effecting": { "travel.book_hotel": { "spend_cents": lambda args: args["estimated_price_cents"], "evidence_preset": "cost_and_completion", }, }, } ) run = await paybond.agent_run.bind({...}) async def book_hotel(args: dict, _extra) -> dict: payload = await _book_hotel_impl(args) return { "content": [{"type": "text", "text": json.dumps(payload)}], "structuredContent": payload, } sdk_tools = [ tool("travel.book_hotel", "Book a hotel", {"estimated_price_cents": int}, book_hotel), ] config = create_paybond_claude_agents_config(run, sdk_tools) # query(..., options={"mcp_servers": {"paybond": config.mcp_server}, "allowed_tools": config.allowed_tools})
Approval holds
When Paybond returns an approval hold, the wrapped handler returns an MCP error block with isError: true. After operator approval in the tenant console:
run.storeApprovalToken(toolCallId, approvalTokenFromConsole); // Retry the same tool call with the stored token.
run.store_approval_token(tool_call_id, approval_token_from_console) # Retry the same tool call with the stored token.
Wrapped handlers read the token via run.getApprovalToken / run.get_approval_token for the same toolCallId.
Scaffold and smoke
paybond init agent-middleware --framework claude-agents --out ./paybond-claude-agents.ts paybond agent demo claude-agents smoke \ --operation paid-tool \ --requested-spend-cents 100 \ --evidence-preset cost_and_completion \ --format json
The smoke command invokes a wrapped SDK tool handler directly — no live LLM or Anthropic API key in CI.
Example apps: examples/paybond-kit-claude-agents-typescript, examples/paybond-kit-claude-agents-python.
Related
- Agent-agnostic adapter — default when not using the Claude Agent SDK
- Mastra adapter
- CrewAI adapter
- Cloudflare Agents adapter
- Agent middleware — run binding, registry, interceptor
- MCP server — stdio or Streamable HTTP MCP for external hosts (Claude Desktop, Codex, remote hosts)
- Support matrix — install surfaces and smoke commands