Anthropic API

    There are two routes. In the first, Anthropic's platform calls our MCP server. In the second, you give Claude our tool definitions and your own code makes each call. Choose the second when you need the key and the data to stay in your code, or when zero data retention matters: Anthropic's MCP connector and Agent Skills are not eligible for zero data retention.

    Not open yet. Opens with early access.

    On this page

    Before you start

    • An Anthropic API key. The examples use the model claude-sonnet-5-5; replace it with yours.
    • An Understanding API key from the console. You need an account first. Keep it on your server.
    • No sign-in. Both routes use the key. For claude.ai and Claude Desktop, which sign in, see Claude.

    Set it up

    Route one: the MCP connector

    The connector is a beta. Send the header anthropic-beta: mcp-client-2025-11-20. A later version, mcp-client-2026-09-15, also pins a server's tool list, so a changed schema cannot alter behavior in the middle of a conversation; that suits a regulated application.

    curl https://api.anthropic.com/v1/messages \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "anthropic-beta: mcp-client-2025-11-20" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "claude-sonnet-5-5",
        "max_tokens": 1000,
        "messages": [{ "role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }],
        "mcp_servers": [{
          "type": "url",
          "url": "https://api.theunderstanding.company/mcp/claude",
          "name": "understanding",
          "authorization_token": "'"$UNDERSTANDING_API_KEY"'"
        }],
        "tools": [{ "type": "mcp_toolset", "mcp_server_name": "understanding" }]
      }'
    import os
    import anthropic
    
    client = anthropic.Anthropic()
    response = client.beta.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=1000,
        messages=[{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}],
        mcp_servers=[{
            "type": "url",
            "url": "https://api.theunderstanding.company/mcp/claude",
            "name": "understanding",
            "authorization_token": os.environ["UNDERSTANDING_API_KEY"],
        }],
        tools=[{"type": "mcp_toolset", "mcp_server_name": "understanding"}],
        betas=["mcp-client-2025-11-20"],
    )
    print(response.content)

    Things that trip people up:

    • Each server needs exactly one mcp_toolset. To allow only some tools, set default_config to { "enabled": false } and enable the tools you want under configs. To block one, set that tool's enabled to false.
    • authorization_token is documented as an OAuth token. Test one call with your key. The server also accepts a bare key.
    • Every tool the address offers is enabled by default. /mcp/claude offers eleven tools, including the three health tools (score_signal_baseline, route_member_message and check_document_complete). If your application is not meant to send health data, set default_config to { "enabled": false } and enable only the banking tools. The MCP connector is not zero data retention, which matters most for health content.
    • The URL must be public and HTTPS. The connector supports tool calls only, not resources or prompts.
    • Not zero data retention. Tool definitions and results are retained under Anthropic's standard policy. Use route two if you need otherwise.

    Route two: client tools

    The definitions come from our server in Anthropic's shape (name, description, input_schema):

    Bash
    curl "https://api.theunderstanding.company/v1/tools?format=anthropic"

    The reply is { "format": "anthropic", "tools": [ ... ], "execute": { ... } }. When Claude stops with stop_reason of tool_use, send each tool's input to POST https://api.theunderstanding.company/v1/tools/{name} with your key, and return the answer as a tool_result. Set is_error when the call failed. Save this as understanding-anthropic.mjs:

    const API = "https://api.theunderstanding.company";
    const ours = (path, init = {}) =>
      fetch(API + path, {
        ...init,
        headers: { Authorization: `Bearer ${process.env.UNDERSTANDING_API_KEY}`, "Content-Type": "application/json" },
      });
    
    const { tools } = await (await ours("/v1/tools?format=anthropic")).json();
    
    async function runTool(name, input) {
      const res = await ours(`/v1/tools/${name}`, { method: "POST", body: JSON.stringify(input) });
      const body = await res.json();
      if (!res.ok) return { content: JSON.stringify({ error: body.error }), is_error: true };
      const { abstain, ...rest } = body; // abstain first
      return { content: JSON.stringify({ abstain, ...rest }) };
    }
    
    async function message(messages) {
      const res = await fetch("https://api.anthropic.com/v1/messages", {
        method: "POST",
        headers: { "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json" },
        body: JSON.stringify({ model: "claude-sonnet-5-5", max_tokens: 1000, tools, messages }),
      });
      const json = await res.json();
      if (!res.ok) throw new Error(json.error?.message ?? `HTTP ${res.status}`);
      return json;
    }
    
    const messages = [{ role: "user", content: "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }];
    let reply = await message(messages);
    for (let round = 0; round < 5 && reply.stop_reason === "tool_use"; round++) {
      messages.push({ role: "assistant", content: reply.content });
      const results = [];
      for (const block of reply.content.filter((b) => b.type === "tool_use")) {
        results.push({ type: "tool_result", tool_use_id: block.id, ...(await runTool(block.name, block.input)) });
      }
      messages.push({ role: "user", content: results });
      reply = await message(messages);
    }
    console.log(reply.content.filter((b) => b.type === "text").map((b) => b.text).join("\n"));
    import json
    import os
    
    import anthropic
    import requests
    
    API = "https://api.theunderstanding.company"
    OURS = {"Authorization": f"Bearer {os.environ['UNDERSTANDING_API_KEY']}", "Content-Type": "application/json"}
    client = anthropic.Anthropic()
    
    tools = requests.get(f"{API}/v1/tools?format=anthropic", timeout=30).json()["tools"]
    
    def run_tool(name, tool_input):
        res = requests.post(f"{API}/v1/tools/{name}", headers=OURS, json=tool_input, timeout=60)
        body = res.json()
        if not res.ok:
            return {"content": json.dumps({"error": body["error"]}), "is_error": True}
        return {"content": json.dumps({"abstain": body["abstain"], **{k: v for k, v in body.items() if k != "abstain"}})}
    
    messages = [{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}]
    reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages)
    for _ in range(5):
        if reply.stop_reason != "tool_use":
            break
        messages.append({"role": "assistant", "content": reply.content})
        results = [
            {"type": "tool_result", "tool_use_id": b.id, **run_tool(b.name, b.input)}
            for b in reply.content
            if b.type == "tool_use"
        ]
        messages.append({"role": "user", "content": results})
        reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages)
    
    print("".join(b.text for b in reply.content if b.type == "text"))

    Notes on route two:

    • The definitions are not strict. Anthropic's format is the plain schema. You can add "strict": true to a tool to have Claude's arguments conform to it.
    • Force a tool when a pipeline needs it. tool_choice set to { "type": "tool", "name": "check_restricted_merchant" } makes extraction deterministic. Use auto for an interactive assistant.
    • For many items, skip the model. If you already know which question you are asking of a list, call the batch endpoint and use Claude only for the part that needs language.

    What we tuned for this service

    Route one uses /mcp/claude: short descriptions that say what a tool does and give no instructions, and one compact JSON object per result with the abstain flag first. See Claude. The guidance on when to call each tool and what to do on an abstain belongs in your system prompt or in the skill, not in the tool descriptions. Route two uses our standard descriptions.

    Check that it works

    The model should call check_restricted_merchant with merchant set to DRAFTKINGS and mcc set to 7995. Over route one the response holds an mcp_tool_use block and an mcp_tool_result block. The result text is one JSON object:

    JSON
    {"abstain":false,"answer":true,"probability":0.97,"reasons":["mcc_7995","rule_gambling_category","kind_sportsbook"],"decision":"restricted_merchant","type":"predicate"}

    For a case that abstains, ask about LUCKY COINS LLC with code 5816: abstain is true, and Claude should say it is unsure. The numbers show the format. They are not measured results.

    If it doesn't connect

    • The API rejects the request about mcp_servers. Check the anthropic-beta header and that the server is type url with an HTTPS address. Each server needs one mcp_toolset in tools.
    • The tool result says unauthorized. The key did not arrive as a Bearer token. Try a different field, or use route two. Check it with curl https://api.theunderstanding.company/v1/usage -H "Authorization: Bearer $UNDERSTANDING_API_KEY".
    • A call fails with sensitive_data. The arguments held what looks like a card, Social Security or account number. Remove it. See Errors.
    • The model never calls the tool. Say in your system prompt when to use it. Descriptions on our side do not instruct, on purpose.
    • Too many requests. The limit is 60 requests a minute per key. See Rate limits.