home / skills / picahq / skills / claude-agents-sdk

claude-agents-sdk skill

/skills/claude-agents-sdk

This skill helps you integrate PICA MCP with the Anthropic Claude Agents SDK, enabling tool calls and seamless agent tool management.

npx playbooks add skill picahq/skills --skill claude-agents-sdk

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.2 KB
---
name: pica-claude-agents
description: Integrate PICA into an application using the Anthropic SDK (Claude Agents). Use when adding PICA tools to a Claude agent via @anthropic-ai/sdk, setting up PICA MCP with the Anthropic TypeScript SDK, or when the user mentions PICA with Claude Agents SDK.
---

# PICA MCP Integration with the Anthropic SDK (Claude Agents)

PICA provides a unified API platform that connects AI agents to third-party services (CRMs, email, calendars, databases, etc.) through MCP tool calling.

## PICA MCP Server

PICA exposes its capabilities through an MCP server distributed as `@picahq/mcp`. It uses **stdio transport** — it runs as a local subprocess via `npx`.

### MCP Configuration

```json
{
  "mcpServers": {
    "pica": {
      "command": "npx",
      "args": ["@picahq/mcp"],
      "env": {
        "PICA_SECRET": "your-pica-secret-key"
      }
    }
  }
}
```

- **Package**: `@picahq/mcp` (run via `npx`, no install needed)
- **Auth**: `PICA_SECRET` environment variable (obtain from the PICA dashboard https://app.picaos.com/settings/api-keys)
- **Transport**: stdio (standard input/output)

### Environment Variable

Always store the PICA secret in an environment variable, never hardcode it:

```
PICA_SECRET=sk_test_...
```

Add it to `.env.local` (or equivalent) and document it in `.env.example`.

## Using PICA with the Anthropic SDK

The Anthropic TypeScript SDK (`@anthropic-ai/sdk`) connects to MCP servers via the `@modelcontextprotocol/sdk` package.

### Required packages

```bash
pnpm add @anthropic-ai/sdk @modelcontextprotocol/sdk
```

### Before implementing: look up the latest docs

The Anthropic SDK and MCP SDK APIs may change between versions. **Always search the official docs first** to get the current API before writing any code:

- Anthropic SDK: https://github.com/anthropics/anthropic-sdk-typescript
- MCP SDK: https://github.com/modelcontextprotocol/typescript-sdk

### Integration pattern

1. **Create an MCP client** using `Client` from `@modelcontextprotocol/sdk` with `StdioClientTransport` pointed at `npx @picahq/mcp`
2. **List tools** from the MCP client via `client.listTools()`
3. **Convert tools** to Anthropic's `Tool[]` format (map `name`, `description`, `inputSchema`)
4. **Stream responses** using `anthropic.messages.create({ stream: true })` with the converted tools
5. **Handle the agent loop** — when the model returns `tool_use` blocks, execute them via `mcpClient.callTool()`, append results, and loop back
6. **Close the MCP client** when the conversation turn is finished

When passing environment variables to the stdio transport, spread `process.env` so the subprocess inherits the full environment (PATH, etc.):

```typescript
env: {
  ...process.env as Record<string, string>,
  PICA_SECRET: process.env.PICA_SECRET!,
}
```

### Minimal example

```typescript
import Anthropic from "@anthropic-ai/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const anthropic = new Anthropic();

// Connect to PICA MCP server
const transport = new StdioClientTransport({
  command: "npx",
  args: ["@picahq/mcp"],
  env: {
    ...process.env as Record<string, string>,
    PICA_SECRET: process.env.PICA_SECRET!,
  },
});

const mcpClient = new Client({ name: "my-agent", version: "1.0.0" });
await mcpClient.connect(transport);

// Get tools in Anthropic format
const { tools: mcpToolsList } = await mcpClient.listTools();
const tools: Anthropic.Tool[] = mcpToolsList.map((t) => ({
  name: t.name,
  description: t.description || "",
  input_schema: t.inputSchema as Anthropic.Tool.InputSchema,
}));

// Agent loop with streaming
let messages: Anthropic.MessageParam[] = [
  { role: "user", content: "List my connected integrations" },
];

let stepsRemaining = 5;
while (stepsRemaining-- > 0) {
  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 4096,
    messages,
    tools,
    stream: true,
  });

  const assistantContent: Anthropic.ContentBlock[] = [];
  const toolUseBlocks: { id: string; name: string; input: Record<string, unknown> }[] = [];
  let currentText = "";
  let currentToolId = "";
  let currentToolName = "";
  let currentToolInputJson = "";
  let currentBlockType: "text" | "tool_use" | null = null;

  for await (const event of response) {
    switch (event.type) {
      case "content_block_start":
        if (event.content_block.type === "text") {
          currentBlockType = "text";
          currentText = "";
        } else if (event.content_block.type === "tool_use") {
          currentBlockType = "tool_use";
          currentToolId = event.content_block.id;
          currentToolName = event.content_block.name;
          currentToolInputJson = "";
        }
        break;
      case "content_block_delta":
        if (event.delta.type === "text_delta") {
          currentText += event.delta.text;
          // Stream text chunk to client here
        } else if (event.delta.type === "input_json_delta") {
          currentToolInputJson += event.delta.partial_json;
        }
        break;
      case "content_block_stop":
        if (currentBlockType === "text") {
          assistantContent.push({ type: "text", text: currentText } as Anthropic.TextBlock);
        } else if (currentBlockType === "tool_use") {
          const input = JSON.parse(currentToolInputJson || "{}");
          assistantContent.push({
            type: "tool_use", id: currentToolId, name: currentToolName, input,
          } as Anthropic.ToolUseBlock);
          toolUseBlocks.push({ id: currentToolId, name: currentToolName, input });
        }
        currentBlockType = null;
        break;
    }
  }

  // No tool calls — done
  if (toolUseBlocks.length === 0) break;

  // Append assistant message
  messages.push({ role: "assistant", content: assistantContent });

  // Execute tools via MCP and collect results
  const toolResults: Anthropic.ToolResultBlockParam[] = [];
  for (const tool of toolUseBlocks) {
    const result = await mcpClient.callTool({ name: tool.name, arguments: tool.input });
    const output = (result.content as { type: string; text?: string }[])
      .map((c) => (c.type === "text" ? c.text : JSON.stringify(c)))
      .join("\n");
    toolResults.push({ type: "tool_result", tool_use_id: tool.id, content: output });
  }

  // Append tool results
  messages.push({ role: "user", content: toolResults });
}

await mcpClient.close();
```

### Streaming SSE events for a chat UI

When building a Next.js API route, stream responses as SSE events using a `ReadableStream`. Emit events in this format for compatibility with the `PythonChat` frontend component:

- `{ type: "text", content: "..." }` — streamed text chunks
- `{ type: "tool_start", name: "tool_name", input: "..." }` — tool execution starting
- `{ type: "tool_end", name: "tool_name", output: "..." }` — tool execution result
- `{ type: "error", content: "..." }` — error messages
- `data: [DONE]` — stream finished

### Key types

```typescript
// Tool conversion: MCP tool → Anthropic tool
const tools: Anthropic.Tool[] = mcpToolsList.map((t) => ({
  name: t.name,
  description: t.description || "",
  input_schema: t.inputSchema as Anthropic.Tool.InputSchema,
}));

// Tool results sent back as user messages
const toolResult: Anthropic.ToolResultBlockParam = {
  type: "tool_result",
  tool_use_id: toolUseBlock.id,
  content: "result text",
  is_error: false, // set true for errors
};
```

## Checklist

When setting up PICA MCP with the Anthropic SDK:

- [ ] `@anthropic-ai/sdk` is installed
- [ ] `@modelcontextprotocol/sdk` is installed
- [ ] `ANTHROPIC_API_KEY` is set in `.env.local`
- [ ] `PICA_SECRET` is set in `.env.local`
- [ ] `.env.example` documents both `ANTHROPIC_API_KEY` and `PICA_SECRET`
- [ ] MCP client uses stdio transport with `npx @picahq/mcp`
- [ ] Full `process.env` is spread into the transport's `env` option
- [ ] MCP tools are converted to `Anthropic.Tool[]` format
- [ ] Agent loop handles `tool_use` blocks and calls `mcpClient.callTool()`
- [ ] Tool results are sent back as `tool_result` blocks in a `user` message
- [ ] Loop has a step limit to prevent runaway execution
- [ ] MCP client is closed after use (`finally` block)
- [ ] Streaming handles `content_block_start`, `content_block_delta`, `content_block_stop` events

Overview

This skill shows how to integrate PICA MCP into an application using the Anthropic (Claude Agents) TypeScript SDK. It explains running the PICA MCP subprocess via stdio, converting MCP tools into Anthropic tools, and handling a streaming agent loop that executes tool calls via the MCP client. Follow the pattern to securely provide PICA credentials and safely manage tool calls in agent conversations.

How this skill works

The integration launches the PICA MCP server as a subprocess using npx @picahq/mcp and communicates over stdio via the MCP SDK transport. It lists available MCP tools, maps them into Anthropic.Tool[] format, and passes them to the Anthropic messages.create streaming API. When the model emits tool_use blocks, the code executes the corresponding tool via mcpClient.callTool(), appends tool results as tool_result blocks in a user message, and continues the agent loop until no more tool uses or a step limit is reached.

When to use it

  • Adding PICA-provided integrations (CRMs, email, calendars, databases) to a Claude agent.
  • Building a streaming chat UI that must run model-initiated tool calls through PICA.
  • Prototyping connectors quickly without installing the MCP package permanently (npx usage).
  • When you need to convert MCP tool metadata into Anthropic's tool schema.
  • Implementing safe loop control and environment propagation for subprocess tools.

Best practices

  • Never hardcode PICA_SECRET; store it in environment variables and document it in .env.example.
  • Spread process.env into the transport env so the subprocess inherits PATH and other required vars.
  • Enforce a step limit in the agent loop to prevent runaway tool execution.
  • Close the MCP client in a finally block to ensure subprocess cleanup.
  • Stream content blocks to the client immediately and emit structured SSE events for UI compatibility.

Example use cases

  • A helpdesk agent that lists connected integrations and calls CRM or support tools through PICA.
  • A calendar assistant that uses PICA tools to read and create events triggered by model tool_use responses.
  • A dashboard that streams a Claude agent response while executing database queries via MCP tools.
  • A prototype chat UI in Next.js that emits SSE events for text, tool_start, and tool_end.

FAQ

How do I provide the PICA secret to the MCP subprocess?

Set PICA_SECRET in your environment (for example .env.local) and pass it into the stdio transport env by spreading process.env and adding PICA_SECRET.

Do I need to install @picahq/mcp?

No. Use npx @picahq/mcp to run the MCP server as a subprocess without adding it to your dependencies.