Paybond integrates with the Claude Agent SDK by wrapping paid custom tools registered via tool() and bundling them into an in-process MCP server. Built-in Claude tools (Read, Bash, etc.) pass through unguarded.
Beta: The Claude Agent SDK is alpha on PyPI and evolving on npm. Treat this adapter as beta until Anthropic stabilizes the SDK.
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.
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 + 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 passmcpServer+allowedToolstoquery().
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 Harbor 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.
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 (planned)
- CrewAI adapter (planned)
- Cloudflare Agents adapter (planned)
- Agent middleware — run binding, registry, interceptor
- MCP server — stdio MCP for external hosts (Claude Desktop, Codex)
- Support matrix — install surfaces and smoke commands