Skip to content

Anthropic Claude API — Tool Use

Anthropic 의 Messages API 는 tools 파라미터를 통해 임의 도구를 LLM 에 노출한다. ainote OpenAPI 3.1 mirror 의 도구 정의를 그대로 변환해 사용 가능 — Anthropic 진영의 모든 직접 API 호출 시나리오 (자체 봇 / SaaS / agentic app / 사내 운영) 가 ainote 를 백엔드로 사용 가능.

핵심 아이디어

Anthropic tools 형식은 OpenAPI 3.1 paths 와 거의 1:1 매핑된다:

Anthropic tools[].fieldOpenAPI paths[/{path}].post.field
namepath 마지막 segment (예: handoff_save)
descriptionsummary 또는 description
input_schemarequestBody.content["application/json"].schema

Anthropic 이 직접 OpenAPI 를 import 하지는 않으므로 한 번 변환만 하면 된다.

Python 예시

python
import requests
import anthropic

# 1. ainote OpenAPI spec 가져오기
spec = requests.get("https://api.ainote.dev/api/mcp/openapi.json").json()

# 2. paths -> Anthropic tools 변환
tools = []
for path, ops in spec["paths"].items():
    op = ops["post"]
    name = path.split("/")[-1]  # /api/mcp/tools/handoff_save -> handoff_save
    body_schema = op["requestBody"]["content"]["application/json"]["schema"]

    tools.append({
        "name": name,
        "description": op.get("description") or op.get("summary", ""),
        "input_schema": body_schema,
    })

# 3. Anthropic Messages API 호출
client = anthropic.Anthropic()  # ANTHROPIC_API_KEY 환경변수
AINOTE_KEY = "<YOUR_MCP_KEY>"

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    tools=tools,
    messages=[{"role": "user", "content": "오늘 마감 태스크 보여줘"}],
)

# 4. tool_use 응답 시 ainote 에 호출
for block in response.content:
    if block.type == "tool_use":
        tool_name = block.name        # 예: "list_tasks"
        tool_input = block.input      # 예: {"due_today": True}

        r = requests.post(
            f"https://api.ainote.dev/api/mcp/tools/{tool_name}",
            json=tool_input,
            headers={"Authorization": f"McpKey {AINOTE_KEY}"},
        )
        print(r.json())

annotations 활용 — destructive 게이팅

Anthropic API 응답에서 tool_use 가 destructive 한 도구일 때 사용자 confirmation 요구:

python
DESTRUCTIVE = {
    name for path, ops in spec["paths"].items()
    if (op := ops["post"])
    and (ann := op.get("x-mcp-annotations", {}))
    and ann.get("destructiveHint")
    for name in [path.split("/")[-1]]
}

for block in response.content:
    if block.type == "tool_use":
        if block.name in DESTRUCTIVE:
            # 사용자에게 확인 후 호출
            confirm = input(f"⚠️ {block.name} 호출하시겠습니까? (y/n): ")
            if confirm.lower() != "y":
                continue
        # 호출 진행 ...

Anthropic SDK — TypeScript

ts
import Anthropic from "@anthropic-ai/sdk";

const spec = await fetch("https://api.ainote.dev/api/mcp/openapi.json").then(r => r.json());

const tools = Object.entries(spec.paths).map(([path, ops]: [string, any]) => {
  const op = ops.post;
  return {
    name: path.split("/").pop()!,
    description: op.description || op.summary || "",
    input_schema: op.requestBody.content["application/json"].schema,
  };
});

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const AINOTE_KEY = process.env.AINOTE_API_KEY!;

const msg = await client.messages.create({
  model: "claude-opus-4-7",
  max_tokens: 4096,
  tools,
  messages: [{ role: "user", content: "save handoff for this session" }],
});

for (const block of msg.content) {
  if (block.type === "tool_use") {
    const res = await fetch(`https://api.ainote.dev/api/mcp/tools/${block.name}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `McpKey ${AINOTE_KEY}`,
      },
      body: JSON.stringify(block.input),
    });
    console.log(await res.json());
  }
}

Tool result feedback (multi-turn)

Anthropic 가 tool 결과를 본 다음 응답을 만들도록 follow-up 메시지를 추가:

python
# 도구 호출 결과
tool_result = r.json()  # ainote 응답

# Anthropic 에 결과 전달 → 다음 응답 생성
followup = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    tools=tools,
    messages=[
        {"role": "user", "content": "오늘 마감 태스크 보여줘"},
        {"role": "assistant", "content": response.content},
        {"role": "user", "content": [
            {
                "type": "tool_result",
                "tool_use_id": block.id,  # 위 tool_use block 의 id
                "content": str(tool_result),
            }
        ]},
    ],
)

MCP HTTP transport vs OpenAPI 변환 — 둘 중 어느 것?

방식장점단점
MCP HTTP (api.ainote.dev/api/mcp)JSON-RPC 표준, tools/list 동적 발견, annotations 그대로 wireAnthropic API 의 tools 파라미터가 MCP 를 직접 이해 못 함 (2026-05 현재) — 별도 어댑터 필요
OpenAPI 변환 (위 예시)Anthropic API 의 tools 파라미터에 한 번 변환만 하면 통합 끝. annotations 는 x-mcp-annotations 로 직접 조회 가능OpenAPI spec 변경 시 재변환 필요 (캐시 + 주기적 fetch)

추천: 대부분의 경우 OpenAPI 변환이 단순. MCP HTTP 는 Claude Code / Claude Desktop 같은 MCP-native 클라이언트에만 사용.

자주 묻는 문제

"input_schema validation error" — Anthropic 의 input_schema 는 OpenAPI 의 requestBody.content["application/json"].schema 와 거의 같지만, 일부 OpenAPI-only 필드 (example, examples, x-* extension) 는 dropped. 변환 시 안전.

"Rate limit 429" — Anthropic 측 API rate limit + ainote 측 fair-use 한도 둘 다 체크. Phase 3 후 X-RateLimit-* 헤더로 ainote 측 quota 노출 예정.

"맥OS Keychain 에서 key 어떻게?" — Python keyring 라이브러리 + macOS security find-generic-password -s @ainote/cli.

전체 도구 매트릭스 · LangChain 진영