Claude Agent SDK tools
The outbound tools as an in-process MCP server for the Claude Agent SDK, with the list of tool names to pre-approve.
outbound-sdk/tools/claude-agent gives the tools to the Claude Agent SDK as an in-process MCP server. Nothing is spawned and no port is opened: the server lives in your process.
This subpath imports @anthropic-ai/claude-agent-sdk, which is an optional peer dependency. Install it only if you import this subpath. See installation.
| Export | What it returns |
|---|---|
claudeAgentTools(outbound, options) |
server to pass as options.mcpServers[serverName], allowedTools to pass as options.allowedTools, and serverName. |
claudeToolDefinitions(outbound, options) |
The tool definitions on their own, for an MCP server you build yourself with createSdkMcpServer. |
Both take the only and extra options of createTools. claudeAgentTools also takes serverName, which defaults to outbound. The agent addresses each tool as mcp__<serverName>__<tool>.
Build the server and run a tool
import { createOutbound } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";
import {
claudeAgentTools,
claudeToolDefinitions,
} from "outbound-sdk/tools/claude-agent";
const outbound = createOutbound({
adapter: memory({ data: demoData(), workspaceId: "acme" }),
});
// An in-process MCP server and the names to pre-approve.
const tools = claudeAgentTools(outbound, {
only: ["campaign_overview", "sender_health"],
});
console.log(tools.serverName, tools.server.type, tools.allowedTools);
// `serverName` changes the prefix the agent addresses the tools by.
console.log(
claudeAgentTools(outbound, { only: ["reply_samples"], serverName: "sends" })
.allowedTools
);
// The definitions on their own, to add to an MCP server you build yourself.
const definitions = claudeToolDefinitions(outbound);
const overview = definitions.find((tool) => tool.name === "campaign_overview");
console.log(definitions.length, "definitions;", overview?.annotations);
// What the Agent SDK does when the model calls the tool, done by hand here.
const result = await overview?.handler({ status: "paused" }, undefined);
console.log(JSON.stringify(result, null, 2));
const failed = await overview?.handler({ client: "acme" }, undefined);
console.log(failed?.isError, failed?.content[0]);
outbound sdk [ 'mcp__outbound__campaign_overview', 'mcp__outbound__sender_health' ]
[ 'mcp__sends__reply_samples' ]
9 definitions; { readOnlyHint: true }
{
"content": [
{
"text": "{\"data\":{\"campaigns\":[{\"campaign\":\"memory:acme:cmp_agencies\",\"channel\":\"email\",\"name\":\"Agency owners\",\"metrics\":{\"bounce_rate\":0.0667,\"bounced\":4,\"connections_accepted\":null,\"connections_sent\":null,\"contacted\":60,\"opened\":18,\"positive\":0,\"positive_reply_rate\":0,\"replied\":1,\"reply_rate\":0.0167,\"reply_rate_basis\":\"contacted\",\"sent\":60},\"status\":\"paused\"}]},\"meta\":{\"evidence\":\"exact\",\"provider\":\"memory\",\"requests\":1,\"today\":\"2026-11-14\"},\"next\":\"Use campaign_trend for one campaign over time, or winning_steps to see which step drives replies.\",\"ok\":true}",
"type": "text"
}
],
"isError": false
}
true {
text: '{"error":{"code":"Invalid","message":"campaign_overview: \\"client\\" is not an input. It takes: from, limit, status, to"},"next":"Check the input and try again.","ok":false}',
type: 'text'
}
- Every tool is marked
readOnlyHint: true. All of them only read, which is whyallowedToolslists every one: you can pre-approve the whole set. - A result is MCP text content. The tool’s result object is serialized as JSON in one
textitem. - A failed tool sets
isError: true. The content is still the{ ok: false, error, next }object, so the model can read why.
Wiring it to an agent
import { query } from "@anthropic-ai/claude-agent-sdk";
import { createOutbound } from "outbound-sdk";
import { instantly } from "outbound-sdk/instantly";
import { claudeAgentTools } from "outbound-sdk/tools/claude-agent";
// Reads INSTANTLY_API_KEY. Constructing it makes no request.
const outbound = createOutbound({ adapter: instantly() });
const tools = claudeAgentTools(outbound);
/** Wiring only: the test suite defines this function and never calls it. */
export const ask = async (prompt: string) => {
const answers: string[] = [];
for await (const message of query({
options: {
allowedTools: tools.allowedTools,
mcpServers: { [tools.serverName]: tools.server },
},
prompt,
})) {
if (message.type === "result" && message.subtype === "success") {
answers.push(message.result);
}
}
return answers.join("\n");
};
To serve the same tools to Claude Code, Claude Desktop or another MCP client without writing code, run outbound mcp.
Reply text
A model given reply_samples can ask for reply text. It comes back truncated and marked as untrusted. See untrusted content.