Claude
Use @codespar/claude to give Anthropic Claude agents commerce capabilities in Latin America.
Claude Adapter
The @codespar/claude adapter converts CodeSpar session tools into Anthropic's tool format and handles tool-use blocks in the response loop. It works with the official @anthropic-ai/sdk and provides four exported functions: getTools, toClaudeTool, handleToolUse, and toToolResultBlock.
Installation
npm install @codespar/sdk @codespar/claude @anthropic-ai/sdkpnpm add @codespar/sdk @codespar/claude @anthropic-ai/sdkyarn add @codespar/sdk @codespar/claude @anthropic-ai/sdk[!NOTE]
@codespar/claudehas peer dependencies on@codespar/sdk@^0.2.0and@anthropic-ai/sdk@^0.30.0. Make sure both are installed.
API Reference
getTools(session): Promise<Anthropic.Tool[]>
Fetches all tools from the session and converts them to Anthropic's Tool[] format. Each tool includes a name, description, and input_schema (JSON Schema). This is the primary function you will use -- it handles both fetching and formatting in a single call.
import { CodeSpar } from "@codespar/sdk";
import { getTools } from "@codespar/claude";
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
const session = await codespar.sessions.create({
servers: ["stripe", "mercadopago"],
});
const tools = await getTools(session);
console.log(JSON.stringify(tools[0], null, 2));{
"name": "codespar_checkout",
"description": "Create a checkout session for a product or service",
"input_schema": {
"type": "object",
"properties": {
"provider": {
"type": "string",
"description": "Payment provider to use (e.g., stripe, mercadopago)"
},
"amount": {
"type": "number",
"description": "Amount in cents (e.g., 4990 for R$49.90)"
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g., BRL)"
},
"description": {
"type": "string",
"description": "Product or service description"
},
"payment_methods": {
"type": "array",
"items": { "type": "string" },
"description": "Accepted payment methods (pix, card, boleto)"
}
},
"required": ["provider", "amount", "currency"]
}
}[!WARNING]
getToolsis async because it callssession.tools()under the hood. Alwaysawaitit. Forgetting to await will pass a Promise instead of an array toanthropic.messages.create, causing a runtime error.
toClaudeTool(tool): Anthropic.Tool
Converts a single CodeSpar tool definition to Anthropic's Tool format. Use this when you have already fetched tools via session.tools() and want to convert them individually -- for example, to filter or transform tools before passing them to Claude.
import { toClaudeTool } from "@codespar/claude";
const allTools = await session.tools();
// Filter to only payment-related tools
const paymentTools = allTools
.filter((t) => ["codespar_checkout", "codespar_pay"].includes(t.name))
.map(toClaudeTool);
// Use the filtered set with Claude
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools: paymentTools,
messages,
});The function maps the CodeSpar tool schema to Anthropic's expected format:
// Input: CodeSpar Tool
{ name: string; description: string; input_schema: JSONSchema }
// Output: Anthropic.Tool
{ name: string; description: string; input_schema: JSONSchema }handleToolUse(session, toolUseBlock): Promise<unknown>
Executes a tool-use block from Claude's response against the CodeSpar session. It extracts the tool name and input from the block, calls session.execute(), and returns the raw result.
import { handleToolUse } from "@codespar/claude";
// toolUseBlock comes from Claude's response content
// { type: "tool_use", id: "toolu_abc123", name: "codespar_checkout", input: {...} }
const result = await handleToolUse(session, toolUseBlock);
console.log(JSON.stringify(result, null, 2));{
"checkout_id": "chk_7f8g9h0i1j2k",
"url": "https://checkout.stripe.com/c/pay/cs_live_...",
"amount": 4990,
"currency": "BRL",
"status": "open",
"expires_at": "2026-04-16T14:30:00Z"
}[!TIP] The return type is
unknownbecause each tool returns a different shape. You can cast it to a specific type if you know which tool was called, but typically you pass the result directly totoToolResultBlockwithout inspecting it.
toToolResultBlock(toolUseId, result): Anthropic.ToolResultBlockParam
Wraps a tool result into Anthropic's ToolResultBlockParam format so it can be sent back to Claude in the next message. It JSON-stringifies the result and sets the correct tool_use_id.
import { toToolResultBlock } from "@codespar/claude";
const block = toToolResultBlock(toolUseBlock.id, result);
console.log(JSON.stringify(block, null, 2));{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "{\"checkout_id\":\"chk_7f8g9h0i1j2k\",\"url\":\"https://checkout.stripe.com/c/pay/cs_live_...\",\"amount\":4990,\"currency\":\"BRL\",\"status\":\"open\",\"expires_at\":\"2026-04-16T14:30:00Z\"}"
}For error cases, pass an error object as the result:
const errorBlock = toToolResultBlock(toolUseBlock.id, {
error: "Payment provider returned: insufficient funds",
});
// Claude will see the error and can retry or ask the user for clarificationFull agent loop
This is a complete, production-ready example of a Claude agent that processes commerce operations. It includes error handling, multi-turn conversation, and session cleanup.
import Anthropic from "@anthropic-ai/sdk";
import { CodeSpar } from "@codespar/sdk";
import {
getTools,
handleToolUse,
toToolResultBlock,
} from "@codespar/claude";
const anthropic = new Anthropic();
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
async function run(userMessage: string) {
// 1. Create a session with the servers you need
const session = await codespar.sessions.create({
servers: ["stripe", "mercadopago", "correios"],
});
// 2. Get tools in Anthropic format
const tools = await getTools(session);
// 3. Start the conversation
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage },
];
let response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
system:
"You are a commerce assistant for a Brazilian e-commerce store. " +
"Use the available tools to help the user with payments, invoicing, " +
"and shipping. Always confirm amounts and details before processing. " +
"Respond in the same language the user writes in.",
tools,
messages,
});
// 4. Tool-use loop
while (response.stop_reason === "tool_use") {
const toolUseBlocks = response.content.filter(
(b) => b.type === "tool_use"
);
const toolResults = await Promise.all(
toolUseBlocks.map(async (block) => {
try {
const result = await handleToolUse(session, block);
return toToolResultBlock(block.id, result);
} catch (error) {
return toToolResultBlock(block.id, {
error: error instanceof Error ? error.message : "Tool call failed",
});
}
})
);
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools,
messages,
});
}
// 5. Close the session when done
await session.close();
// 6. Return the final text
const text = response.content.find((b) => b.type === "text");
return text?.type === "text" ? text.text : "";
}
// Usage
const reply = await run("Create a R$99 Pix payment for order #1234");
console.log(reply);Handling parallel tool calls
Claude may return multiple tool_use blocks in a single response when it needs to execute operations in parallel. The adapter handles this naturally -- map over all blocks and return all results:
const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
// Execute all tool calls in parallel
const toolResults = await Promise.all(
toolUseBlocks.map(async (block) => {
const result = await handleToolUse(session, block);
return toToolResultBlock(block.id, result);
})
);
// Send all results back in a single message
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });[!NOTE] Parallel tool calls are common when Claude chains operations. For example, after creating a checkout link, Claude might simultaneously call
codespar_notifyto send it via WhatsApp andcodespar_invoiceto prepare the fiscal document.
Streaming
For real-time output, use anthropic.messages.stream() instead of create(). The tool-use loop works the same way -- you just need to handle the streamed final message differently:
import Anthropic from "@anthropic-ai/sdk";
import { CodeSpar } from "@codespar/sdk";
import { getTools, handleToolUse, toToolResultBlock } from "@codespar/claude";
const anthropic = new Anthropic();
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
async function runStreaming(userMessage: string) {
const session = await codespar.sessions.create({
servers: ["stripe", "mercadopago"],
});
const tools = await getTools(session);
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage },
];
let continueLoop = true;
while (continueLoop) {
const stream = anthropic.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools,
messages,
});
// Stream text chunks to the console
stream.on("text", (text) => process.stdout.write(text));
const response = await stream.finalMessage();
if (response.stop_reason === "tool_use") {
const toolUseBlocks = response.content.filter(
(b) => b.type === "tool_use"
);
const toolResults = await Promise.all(
toolUseBlocks.map(async (block) => {
try {
const result = await handleToolUse(session, block);
return toToolResultBlock(block.id, result);
} catch (error) {
return toToolResultBlock(block.id, {
error: error instanceof Error ? error.message : "Tool call failed",
});
}
})
);
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
} else {
continueLoop = false;
}
}
await session.close();
}
await runStreaming("Create a R$49.90 checkout link for the Pro Plan");Error handling
There are three categories of errors to handle:
1. Session errors
These occur when creating or using a session:
try {
const session = await codespar.sessions.create({
servers: ["stripe"],
});
} catch (error) {
if (error instanceof Error) {
// INVALID_API_KEY, RATE_LIMITED, SERVER_NOT_FOUND
console.error("Session error:", error.message);
}
}2. Tool execution errors
These occur when a tool call fails (e.g., invalid parameters, provider error). The best practice is to return the error as a tool result so Claude can reason about it:
const toolResults = await Promise.all(
toolUseBlocks.map(async (block) => {
try {
const result = await handleToolUse(session, block);
return toToolResultBlock(block.id, result);
} catch (error) {
// Return the error to Claude instead of throwing
return toToolResultBlock(block.id, {
error: error instanceof Error ? error.message : "Tool call failed",
tool_name: block.name,
input: block.input,
});
}
})
);[!TIP] Returning errors as tool results (instead of throwing) lets Claude reason about the failure. Claude may retry with different parameters, ask the user for clarification, or suggest an alternative approach.
3. Anthropic API errors
These are standard SDK errors (rate limits, context length, etc.):
try {
response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools,
messages,
});
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
// Implement exponential backoff
} else if (error instanceof Anthropic.APIError) {
console.error(`Anthropic API error: ${error.status} ${error.message}`);
}
}Best practices
-
Always close sessions. Use
try/finallyto ensuresession.close()runs even if the agent loop throws. -
Scope servers narrowly. Only connect the servers your agent actually needs.
["stripe"]is better than["stripe", "mercadopago", "asaas", "pagarme"]if you only use Stripe. -
Set a system prompt. Tell Claude what domain it operates in and what tools to prefer. This reduces unnecessary
codespar_discovercalls. -
Return errors as tool results. Never let
handleToolUseexceptions bubble up and crash the loop. Return them as structured tool results so Claude can self-correct. -
Limit loop iterations. Add a maximum iteration count to prevent infinite loops if Claude keeps calling tools:
const MAX_ITERATIONS = 10;
let iterations = 0;
while (response.stop_reason === "tool_use" && iterations < MAX_ITERATIONS) {
// ... tool-use handling ...
iterations++;
}
if (iterations >= MAX_ITERATIONS) {
console.warn("Agent reached maximum tool-call iterations");
}Next steps
- Sessions -- Learn about session lifecycle and server selection
- Tools and Meta-Tools -- Understand what tools are available and how routing works
- OpenAI Adapter -- If you are using GPT models instead
- Vercel AI SDK -- Streaming-first agents for Next.js
- Quickstart -- End-to-end setup in under 5 minutes