Tools

External tool servers

External tool servers extend the catalog with tools the platform itself does not implement. Each server speaks JSON-RPC 2.0, declares the tools it offers, and runs them on its own infrastructure. The chat handler discovers the server's tools at the start of every session, merges them into the catalog with a name prefix, and dispatches calls to the server when the model uses one of the discovered names.

Updated today

Overview

At a glance
Wire protocol
JSON-RPC 2.0 over HTTPS
Client library
src/lib/mcp-client.ts
Methods used
initialize, notifications/initialized, tools/list, tools/call
Tool naming
Server prefix + originalName
Cache lifetime
10 minutes per (server, workspace)
Authentication
Bearer token, custom header, or URL-embedded scoping
Per-user scoping
Workspace code substituted into URL or header at call time

External tool servers are how the catalog grows without shipping new code. A server is anything that speaks the protocol. It can be hosted by us, by a vendor, or by the user. Every server registers a prefix; every tool it returns shows up under that prefix in the merged catalog. The platform does not need to know what the tool does until the model asks for it.

What an external tool server is

A small, focused HTTP endpoint that exposes one or more tools.

An external tool server is a service that answers four JSON-RPC methods. initialize negotiates the protocol version and exchanges capabilities. tools/list returns the schemas the server makes available. tools/call executes a named tool with the provided arguments and returns a result. notifications/initialized is a fire-and-forget signal that the handshake is complete.

The server holds its own state, knows how to call the upstream provider it wraps, and is responsible for its own authentication with that provider. The platform's job is to discover what the server offers, hand the catalog to Claude, and route calls back.

Server registration

A server is registered with a config object stored in env or in a per-workspace table.

src/lib/mcp-client.tsts
36export interface ExternalServerConfig {
37 name: string // logical id used in the prefix
38 url: string // base URL; may contain {workspaceCode}
39 auth?:
40 | { type: "bearer"; envVar: string }
41 | { type: "header"; headerName: string; envVar: string }
42 enabled: boolean
43 toolPrefix?: string // override the default "<name>_"
44 timeout?: number // ms, default 30_000
45}

Handshake

Three calls at the start of a session, run once and cached.

src/lib/mcp-client.tsts
1// 1. initialize → exchange protocol version + capabilities
2await rpc(server, "initialize", {
3 protocolVersion: "2024-11-05",
4 capabilities: {},
5 clientInfo: { name: "ultron", version: "1" },
6})
7
8// 2. notifications/initialized → fire and forget signal
9await rpc(server, "notifications/initialized", {})
10
11// 3. tools/list → returns the array of tool schemas
12const tools = (await rpc(server, "tools/list", {})).tools as ToolSchema[]
Note
The session id returned in the Mcp-Session-Id header on the initialize response is preserved across subsequent calls. Servers that maintain stateful sessions rely on it; stateless servers can ignore it.

Tool discovery

The catalog is merged at the start of every turn.

src/lib/mcp-client.tsts
1export async function getMcpTools(ctx: ToolContext): Promise<OpenAIFunctionSchema[]> {
2 const servers = await loadEnabledServers(ctx)
3 const all: OpenAIFunctionSchema[] = []
4 for (const s of servers) {
5 const tools = await discoverWithCache(s, ctx.workspaceCode)
6 for (const t of tools) {
7 all.push({
8 type: "function",
9 function: {
10 name: `${s.toolPrefix ?? s.name + "_"}${t.name}`,
11 description: t.description,
12 parameters: t.inputSchema ?? { type: "object", properties: {} },
13 },
14 })
15 }
16 }
17 return all
18}

Discovery is cached for ten minutes per (server, workspace). If a server adds or removes tools during the cache window, the change does not surface until the cache expires or the user reconnects the integration. This is a deliberate trade-off: live discovery on every turn would balloon latency for sessions that touch a dozen servers.

Execution

A tool call is routed back to the originating server with the original tool name.

src/lib/mcp-client.tsts
381export async function executeMcpTool(
382 name: string,
383 args: Record<string, unknown>,
384 ctx: ToolContext,
385): Promise<string> {
386 const { server, originalName } = resolveByPrefix(name)
387 const res = await rpc(server, "tools/call", { name: originalName, arguments: args })
388 // Extract first text content block from the response
389 const block = (res.content as Array<{ type: string; text?: string }>).find((b) => b.type === "text")
390 return block?.text ?? JSON.stringify(res)
391}
Warning
External tool calls share the same turn budget as native tool calls. A slow server reduces the work the model can fit in a single turn. Servers should respond within a few seconds; anything longer should stream progress through a side channel and return a placeholder.

Authentication

Three patterns cover every server we register.

PatternWhen to use itHeader set
BearerServer owns the credential, one token per serverAuthorization: Bearer <token>
Custom headerServer expects a vendor specific headerThe header name configured, value from env
URL-embedded scopingServer scopes by URL path or query, no extra headerNone beyond content-type
src/lib/mcp-client.tsts
120const url = config.url.replace("{workspaceCode}", encodeURIComponent(workspaceCode))
121const headers: HeadersInit = { "content-type": "application/json" }
122if (config.auth?.type === "bearer") {
123 headers["authorization"] = `Bearer ${env[config.auth.envVar]}`
124} else if (config.auth?.type === "header") {
125 headers[config.auth.headerName] = env[config.auth.envVar]
126}

Per-user scoping

The same server can return a different tool catalog to different users.

Some servers expect a per-user identifier in the URL so the discovery response only includes tools that the connected user has authorised. The platform substitutes the workspace code into the URL at discovery and execution time. The result is that two users who hit the same server see different tool sets, depending on which apps they have connected through the upstream provider's OAuth flow.

Note
Per-user scoping means the catalog Claude sees is unique to the current user. Skills that depend on a specific external tool should account for the possibility that the user does not have it connected and the tool is therefore absent.

Discovery cache

A small TTL cache keeps the merge fast.

PropertyValue
Cache key(server name, workspace code)
TTL10 minutes
InvalidationUser reconnects an integration, server returns a 401, or admin clears the cache
Cold start costOne full handshake per server on the first turn
Warm costLocal map lookup, no network

Available integrations

The external surface that ships today. Each block shows the upstream service and the kind of work routed through it.

Google

Gmail
Gmail
Mail send + read
Google Drive
Google Drive
File operations
Google Sheets
Google Sheets
Read + batch write
Google Docs
Google Docs
Create + update
Google Analytics
Google Analytics
Accounts + properties
Google Meet
Google Meet
Meetings + transcripts
Google Maps
Google Maps
Places + search

Microsoft

Outlook
Outlook
Mail + folder ops
Microsoft Teams
Microsoft Teams
Channels + replies

Communications

Slack
Slack
Channels + DMs + search
Notion
Notion
Pages + databases
Zoom
Zoom
Meetings + recordings
Telegram
Telegram
Messages + channels
Instagram
Instagram
Insights + media

CRM and outreach

HubSpot
HubSpot
Contacts + deals
Salesforce
Salesforce
Records + reports
Apollo
Apollo
People + companies
Calendly
Calendly
Events + invitees
Intercom
Intercom
Conversations

Data and workflow

Airtable
Airtable
Tables + records
ClickUp
ClickUp
Tasks + spaces
Supabase
Supabase
Schemas + records (read only)
n8n
n8n
Workflow triggers
Apify
Apify
Actors + runs
Reddit
Reddit
Search + comments
Brave Search
Brave Search
Web fallback

Operations

Stripe
Stripe
Customers + invoices (provider module)
Brevo
Brevo
Transactional + campaign mail
PostHog
PostHog
Event analytics
Cloudflare
Cloudflare
Workers AI + R2 + D1
Webflow
Webflow
Sites + items

Limits and errors

What goes wrong and how the platform handles it.

ConditionPlatform behaviour
Server returns 401 or 403Cache is invalidated, tool is dropped from the next merge, user is prompted to reconnect
Server returns 5xxThe call is retried once with exponential backoff, then surfaced to the model as a tool error so it can decide what to do
Server times out30 second cap by default. Hard timeout; the tool result reads as a transient error.
Schema is malformedTool is dropped from the merge with a warning logged. Other tools from the same server still load.
Server is unreachableAll tools from that server are skipped for the current turn. Future turns retry on the cache TTL.

File map

src/lib
mcp-client.tsJSON-RPC client, handshake, discovery cache, execution
chat-tools.tscalls getMcpTools at catalog build, executeMcpTool at dispatch
src/lib/integrations
registry.tsthe list of registered external servers per workspace

Glossary

External tool server
An HTTPS endpoint that answers initialize, tools/list, and tools/call. Provides tools the platform itself does not implement.
Tool prefix
The short string prepended to every tool name the server returns. Used to route calls back to the originating server.
Workspace code
The per-user identifier substituted into a server URL or header so the server can scope its catalog to that user's connected apps.
Discovery cache
A 10-minute TTL cache keyed by (server, workspace) that stores the schemas returned by tools/list.
Handshake
The three-step sequence (initialize, notifications/initialized, tools/list) run once per cache window to learn what a server offers.
Session id
The opaque identifier returned in the Mcp-Session-Id header on initialize. Preserved across subsequent calls for servers that need it.