Beltic logo
SDK GuideIntegrations

Claude Agent SDK

Integrate Beltic signing with Claude Agent SDK for verifiable autonomous agents

Claude Agent SDK Integration

Build autonomous AI agents with Claude Agent SDK that make verifiable HTTP requests to external services.

Overview

The Claude Agent SDK provides powerful built-in tools like Read, Write, Bash, and WebFetch. To make your agent's HTTP requests verifiable by external services, you can integrate Beltic signing using an MCP server.

Prerequisites

  • Claude Code installed
  • Claude Agent SDK installed
  • Beltic CLI installed and authenticated (beltic login)
  • Agent keypair generated (beltic keygen)
  • Key directory hosted at an HTTPS URL

Installation

npm install @anthropic-ai/claude-agent-sdk @belticlabs/agent-signer

Integration Approach: MCP Server

The cleanest integration is creating an MCP server that provides a signed_fetch tool to your agent. This keeps signing logic separate and reusable.

Step 1: Create the MCP Server

Create a file beltic-mcp-server.ts:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { signHttpRequest, importPrivateKey } from "@belticlabs/agent-signer";

const server = new Server(
  { name: "beltic-signer", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Load credentials from environment
const privateKeyPem = process.env.AGENT_PRIVATE_KEY!;
const keyId = process.env.AGENT_KEY_ID!;
const keyDirectoryUrl = process.env.AGENT_KEY_DIRECTORY_URL!;

server.setRequestHandler("tools/list", async () => ({
  tools: [
    {
      name: "signed_fetch",
      description: "Make a signed HTTP request that can be verified by the receiving service",
      inputSchema: {
        type: "object",
        properties: {
          url: { type: "string", description: "The URL to fetch" },
          method: { type: "string", enum: ["GET", "POST", "PUT", "DELETE"], default: "GET" },
          body: { type: "string", description: "Request body (for POST/PUT)" },
          headers: { type: "object", description: "Additional headers" },
        },
        required: ["url"],
      },
    },
  ],
}));

server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "signed_fetch") {
    const { url, method = "GET", body, headers = {} } = request.params.arguments as any;

    try {
      const privateKey = await importPrivateKey(privateKeyPem, "EdDSA");

      const signedHeaders = await signHttpRequest({
        method,
        url,
        headers: { "content-type": "application/json", ...headers },
        body,
        privateKey,
        keyId,
        keyDirectoryUrl,
      });

      const response = await fetch(url, {
        method,
        headers: { ...headers, ...signedHeaders },
        body,
      });

      const data = await response.text();

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({
              status: response.status,
              headers: Object.fromEntries(response.headers),
              body: data,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Error: ${error}` }],
        isError: true,
      };
    }
  }

  throw new Error(`Unknown tool: ${request.params.name}`);
});

const transport = new StdioServerTransport();
await server.connect(transport);

Step 2: Use with Claude Agent SDK

import { query } from "@anthropic-ai/claude-agent-sdk";

// Agent with access to signed HTTP requests
for await (const message of query({
  prompt: "Check my account balance at api.example.com/balance",
  options: {
    allowedTools: ["Read", "Glob"],
    mcpServers: {
      beltic: {
        command: "npx",
        args: ["tsx", "./beltic-mcp-server.ts"],
        env: {
          AGENT_PRIVATE_KEY: process.env.AGENT_PRIVATE_KEY,
          AGENT_KEY_ID: process.env.AGENT_KEY_ID,
          AGENT_KEY_DIRECTORY_URL: process.env.AGENT_KEY_DIRECTORY_URL,
        },
      },
    },
  },
})) {
  if (message.type === "assistant") {
    console.log(message.content);
  }
}

Alternative: Direct Integration

For simpler use cases, you can create a wrapper function:

import { query } from "@anthropic-ai/claude-agent-sdk";
import { signHttpRequest, importPrivateKey } from "@belticlabs/agent-signer";

// Create a signed fetch function
async function signedFetch(url: string, options: RequestInit = {}) {
  const privateKey = await importPrivateKey(process.env.AGENT_PRIVATE_KEY!, "EdDSA");

  const signedHeaders = await signHttpRequest({
    method: (options.method as string) || "GET",
    url,
    headers: options.headers as Record<string, string>,
    body: options.body as string,
    privateKey,
    keyId: process.env.AGENT_KEY_ID!,
    keyDirectoryUrl: process.env.AGENT_KEY_DIRECTORY_URL!,
  });

  return fetch(url, {
    ...options,
    headers: { ...options.headers, ...signedHeaders },
  });
}

// Use the agent, then make signed calls based on its output
for await (const message of query({
  prompt: "What API endpoint should I call to check the balance?",
  options: { allowedTools: ["Read", "Glob"] },
})) {
  // Parse agent response and make signed calls
  if (message.type === "assistant" && message.content.includes("api.example.com")) {
    const response = await signedFetch("https://api.example.com/balance");
    console.log(await response.json());
  }
}

Server-Side Verification

The receiving service verifies requests using @belticlabs/verifier:

import express from "express";
import { createAgentAuthMiddleware, scopeGuard } from "@belticlabs/verifier/express";

const app = express();

// Add Beltic verification middleware
app.use("/api", createAgentAuthMiddleware());

// Protect routes with scope requirements
app.get("/api/balance", scopeGuard("account:read"), (req, res) => {
  // req.agent contains verified agent info
  console.log("Verified agent:", req.agent.id);
  res.json({ balance: 1000 });
});

app.listen(3000);

Environment Variables

Set these environment variables for your agent:

# Your agent's private key (PEM format)
export AGENT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----"

# JWK thumbprint of your public key
export AGENT_KEY_ID="S9Zz0..."

# URL where your key directory is hosted
export AGENT_KEY_DIRECTORY_URL="https://your-agent.com/.well-known/http-message-signatures-directory"

Complete Example

See the beltic-agent-sdk-demo repository for a complete working example.

Next Steps