Claude Agent SDK
Wrap Claude Agent SDK custom tools so the kernel admits each call under a signed capability before the handler runs.
On this page
An agent built with the Claude Agent SDK chooses its tools; a custom tool you build with chioTool runs only after the Chio kernel admits that call. The SDK keeps the agent loop, context management, and its own permission prompts. Chio holds the capability for each tool and signs a receipt for every call, refusals included.
@chio-protocol/claude-agent-sdk wraps the SDK's tool() helper the way @chio-protocol/ai-sdk wraps the Vercel AI SDK. The wrapper sends each call to a Chio sidecar beside the agent process at http://127.0.0.1:9090, and the kernel in the sidecar checks the capability token, runs the guards, and signs the receipt before the handler is called.
1. Install the adapter beside your agent
Install the adapter with the Agent SDK and zod, its two peer dependencies. Run the Chio sidecar next to the process that runs the agent, and give that process the signed capability token for the tools it may call.
npm install @anthropic-ai/claude-agent-sdk zod @chio-protocol/claude-agent-sdk2. Wrap each custom tool and serve it in process
chioTool takes the tool's name, description, zod inputSchema, and an execute handler that returns the tool result, plus a scope that binds the call to a Chio tool server, a tool name, and the capability token. Serve the wrapped tools from an in-process MCP server with createSdkMcpServer.
import { z } from "zod";
import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { chioTool } from "@chio-protocol/claude-agent-sdk";
const searchTool = chioTool({
name: "search",
description: "Search the web",
inputSchema: {
query: z.string().describe("search query"),
},
execute: async ({ query }) => {
const results = await fetchSearchResults(query);
return { content: [{ type: "text", text: JSON.stringify(results) }] };
},
scope: {
toolServer: "web-tools",
toolName: "search",
capabilityToken: process.env.CHIO_CAPABILITY_TOKEN!,
},
});
export const tools = createSdkMcpServer({
name: "web-tools",
version: "1.0.0",
tools: [searchTool],
});The wrapper gates the call at the handler entry point, before any side effect. It returns the definition tool() returns, so the server registers it like any other custom tool, and Claude sees it as mcp__web-tools__search.
3. Run the agent and read each refusal
Pass the server to query and list its tools in allowedTools. Claude can request the tool; the kernel decides whether each call runs.
import { query } from "@anthropic-ai/claude-agent-sdk";
import { tools } from "./tools";
// SDK MCP servers take streaming input, so the prompt is an async generator.
async function* prompt() {
yield {
type: "user" as const,
message: { role: "user" as const, content: "Research quantum computing advances" },
};
}
for await (const message of query({
prompt: prompt(),
options: {
mcpServers: { "web-tools": tools },
allowedTools: ["mcp__web-tools__search"],
},
})) {
if (message.type === "result" && message.subtype === "success") console.log(message.result);
}When the kernel denies a call, the handler throws ChioToolError carrying the verdict, the guard that refused, the reason, and the receipt id. The SDK returns the failure to Claude as an error tool result, so the agent can answer without the tool. Pass onError to record each refusal where your application keeps its audit trail.
import { ChioToolError } from "@chio-protocol/claude-agent-sdk";
const auditedSearch = chioTool({
...searchOptions,
onError: (error) => {
if (error instanceof ChioToolError) {
// error.verdict, error.guard, error.reason, error.receiptId
auditLog.write({ verdict: error.verdict, guard: error.guard, receipt: error.receiptId });
}
},
});Every outcome except an admitted call stops the tool
The wrapper runs the handler only for an allow verdict backed by a signed capability token and verified authorizing evidence. A deny, a pending_approval verdict, a missing token, and an unreachable sidecar each throw ChioToolError.
| Outcome | error.verdict | Handler |
|---|---|---|
| Admitted call | none, the call returns | Runs |
| Kernel denies | deny | Never runs |
| Approval required | pending_approval | Never runs |
Only scope.capabilityId given | incomplete | Never runs |
| Sidecar unreachable | sidecar_unreachable | Never runs |
The SDK's permission layer runs before the handler, and the kernel runs after it. allowedTools, a canUseTool callback, or a permission mode decides whether Claude may attempt the call; none of them grants authority the capability lacks, because the kernel still evaluates the call. Keep approver lists and thresholds in the kernel's approval policy.
Read more in Require human approval.
Adapter reference
Try scope, inputSchema, resolveCapabilityToken, or ChioToolError.
chioTool options
name,description,inputSchema. Forwarded totool().inputSchemais azodraw shape.execute. The handler, called only when Chio admits the call. It returns the MCP tool result, acontentarray.scope. AChioToolScope:toolServer,toolName, andcapabilityToken, the full signed token.client. An optional sharedChioClient.clientOptions. Inline client options:sidecarUrl,timeoutMs,fetch,debug.resolveCapabilityToken. Resolvesscope.capabilityIdto the full raw token when the id is only an indirection key.onError. Called with everyChioToolErrorbefore it is thrown.debug. An optional debug hook; the wrapper never writes to stdout.
Capability id and token
scope.capabilityId is a request hint. The sidecar evaluates deny by default and requires the signed token through scope.capabilityToken or resolveCapabilityToken; an id with no token throws ChioToolError with verdict incomplete before the sidecar is called.
ChioToolError
Thrown when the sidecar denies a call or the transport fails. Fields: verdict, guard (an empty string on transport errors), reason, and receiptId when a signed receipt exists.
ChioClient
A minimal HTTP client for POST /chio/evaluate. Share one client across the tools on a server to reuse its configuration. It accepts the sidecar's EvaluateResponse of verdict, receipt, and evidence.