unlob Docs
Browse documentation

Remote MCP connector

Point the Messages API at the endpoint and skip the tool plumbing entirely.

What this is

Claude’s MCP connector makes the MCP connection server-side. You name the server in your request; Anthropic connects to it, lists its tools, calls them, and feeds the results back into the turn. You never define a tool schema, never write a tool loop, and never see a tool_use block you have to dispatch.

For unlob this is the shortest path from nothing to a research agent.

The request

Two parameters, and they are required together — naming a server without a matching toolset is a validation error.

import os
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=16000,
    betas=["mcp-client-2025-11-20"],
    thinking={"type": "adaptive"},
    mcp_servers=[
        {
            "type": "url",
            "url": "https://api.unlob.com/mcp",
            "name": "unlob",
            # unlob accepts `Authorization: Bearer ulb_…` as an equivalent spelling of
            # `x-api-key`, which is exactly what this field sends.
            "authorization_token": os.environ["UNLOB_API_KEY"],
        }
    ],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "unlob"}],
    messages=[
        {
            "role": "user",
            "content": "What is independently corroborated about the Acme acquisition, "
                       "and which claims trace back to a single source?",
        }
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)

mcp_server_name must match a name in mcp_servers, and every server listed must be referenced by exactly one toolset.

TypeScript is the same shape:

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

const client = new Anthropic();

const response = await client.beta.messages.create({
  model: "claude-opus-5",
  max_tokens: 16000,
  betas: ["mcp-client-2025-11-20"],
  thinking: { type: "adaptive" },
  mcp_servers: [
    {
      type: "url",
      url: "https://api.unlob.com/mcp",
      name: "unlob",
      authorization_token: process.env.UNLOB_API_KEY!,
    },
  ],
  tools: [{ type: "mcp_toolset", mcp_server_name: "unlob" }],
  messages: [{ role: "user", content: "…" }],
});

Narrowing the tool surface

Eleven tools is a lot of schema for a task that only searches. The toolset takes default_config and per-tool configs, so you can run in allowlist mode:

tools=[
    {
        "type": "mcp_toolset",
        "mcp_server_name": "unlob",
        "default_config": {"enabled": False},
        "configs": {
            "web_search": {"enabled": True},
            "corroborate": {"enabled": True},
            "assemble_context": {"enabled": True},
        },
    }
]

Those three cover most research: find, verify, and read. Add dossier when the work is about entities, and get_document when you genuinely need full pages.

Prompting it

The server sends instructions and per-tool descriptions, so Claude already knows what these tools are. Two things are worth adding, because they are judgement calls rather than facts:

Use assemble_context for open questions ("what should I know about X") and
web_search for specific ones ("find the page that says Y").

Before stating anything as established fact, call corroborate on the passage
you are relying on and report how many independent sources carry it.

If a search result has partial: true, part of the corpus was unreachable —
say the answer is incomplete rather than reporting it as complete.

What you give up

The connector is the least code and the least control. Everything happens inside the turn, so you cannot:

  • inspect or filter a tool result before Claude sees it,
  • add caching or retry policy of your own around a call,
  • run a tool conditionally on something outside the conversation.

If you need any of those, define the tools yourself — see Tool use, directly. If you do not, the connector is strictly less code to maintain.

Errors

Refusals from unlob arrive as tool errors inside the turn, not as HTTP failures on your messages.create call. A suspended account or an exhausted quota will show up as an error result that Claude reads and reports — your try/except will not see it.

If Claude says it could not reach the tools, check the endpoint by hand first: Troubleshooting.

Next