Skip to main content
code<spar>
Providers

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/sdk
pnpm add @codespar/sdk @codespar/claude @anthropic-ai/sdk
yarn add @codespar/sdk @codespar/claude @anthropic-ai/sdk

[!NOTE] @codespar/claude has peer dependencies on @codespar/sdk@^0.2.0 and @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));
Output: Anthropic.Tool
{
  "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] getTools is async because it calls session.tools() under the hood. Always await it. Forgetting to await will pass a Promise instead of an array to anthropic.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));
Return value
{
  "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 unknown because 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 to toToolResultBlock without 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));
Output: Anthropic.ToolResultBlockParam
{
  "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 clarification

Full 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.

claude-agent.ts
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_notify to send it via WhatsApp and codespar_invoice to 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:

claude-streaming.ts
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

  1. Always close sessions. Use try/finally to ensure session.close() runs even if the agent loop throws.

  2. Scope servers narrowly. Only connect the servers your agent actually needs. ["stripe"] is better than ["stripe", "mercadopago", "asaas", "pagarme"] if you only use Stripe.

  3. Set a system prompt. Tell Claude what domain it operates in and what tools to prefer. This reduces unnecessary codespar_discover calls.

  4. Return errors as tool results. Never let handleToolUse exceptions bubble up and crash the loop. Return them as structured tool results so Claude can self-correct.

  5. 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