Skip to content
Preview: outbound-sdk is not on npm yet and its repository is private. Instantly and HeyReach are beta, Smartlead is unverified.What is verified
outbound-sdk
Esc
↑↓navigate↵open⌘Jpreview
On this page

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 why allowedTools lists 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 text item.
  • 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.

Was this page helpful?