Connect Claude to Mercury's MCP server
Register Mercury's MCP tools with Claude Desktop or the Claude API's MCP connector.
Mercury exposes its travel-search, booking, and trip tools as an
MCP server at POST /v1/mcp, so a Claude
integration can call searchFlights, listTrips, and the rest of the
assistant tool surface directly instead of you
re-implementing each one as a hand-written function tool.
Authentication
Every call to /v1/mcp carries two independent credentials, same as every
other Mercury /v1 route:
- Consumer HMAC — the
Bizaway-Consumer/Bizaway-Timestamp/
Bizaway-Signatureheaders that identify your integration and are
recomputed per request from a shared secret. Full algorithm and worked
examples: Authentication § Consumer HMAC. - Bearer token — an
Authorization: Bearerheader that is either:- a traveler JWT, obtained through BizAway sign-in (see
Authentication § Obtaining a token). This
unlocks every tool scoped to that traveler —listTrips,
searchFlights,getInvoiceProfiles, and so on. - the shared MCP service token, for a conduit call with no traveler
identity attached. This only reaches non-mutating, non-traveler-scoped
tools; mutating tools (bookings) and every traveler-scoped tool or
resource stay withheld on this path. Most integrations want the
traveler JWT.
- a traveler JWT, obtained through BizAway sign-in (see
Because the consumer signature covers a hash of the exact request body and a
per-request timestamp, it cannot be entered as a static header value the way
a connector's "custom headers" field expects — it has to be computed fresh
for every call. That's also why Claude's native remote-connector field can't
point at /v1/mcp directly yet; see Coming later
below.
Connecting today: a local MCP server that signs each call
Claude Desktop can launch a local MCP server over stdio — a command you
provide in claude_desktop_config.json — instead of only speaking to a
remote URL. That local process holds your consumer secret and traveler JWT,
signs each request the same way any Mercury integration does, and relays
Claude's tools/list / tools/call traffic to POST /v1/mcp over HTTPS.
This is the practical path today for both Claude Desktop and any client built
on the same local-server pattern (e.g. Claude Code); it works with today's
plain-JSON-RPC transport and has no dependency on unshipped server-side work.
claude_desktop_config.json:
{
"mcpServers": {
"mercury": {
"command": "node",
"args": ["/path/to/mercury-mcp-bridge/index.js"],
"env": {
"MERCURY_BASE_URL": "https://api.example.com",
"MERCURY_CONSUMER": "acme-travel",
"MERCURY_CONSUMER_SECRET": "<your consumer secret>",
"MERCURY_TRAVELER_JWT": "<traveler bearer token>"
}
}
}
}The bridge script itself is a thin MCP stdio server: on each tools/call (or
tools/list) message from Claude, it signs and forwards the same call to
Mercury and streams the reply back over stdio. A minimal Node
implementation, using the
MCP TypeScript SDK
for the stdio side and Mercury's own signing algorithm for the HTTP side:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import crypto from "node:crypto";
const { MERCURY_BASE_URL, MERCURY_CONSUMER, MERCURY_CONSUMER_SECRET, MERCURY_TRAVELER_JWT } =
process.env;
function signedHeaders(method, path, body) {
const timestamp = String(Math.floor(Date.now() / 1000));
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const canonical = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
const signature = crypto
.createHmac("sha256", MERCURY_CONSUMER_SECRET)
.update(canonical)
.digest("hex");
return {
"Bizaway-Consumer": MERCURY_CONSUMER,
"Bizaway-Timestamp": timestamp,
"Bizaway-Signature": signature,
Authorization: `Bearer ${MERCURY_TRAVELER_JWT}`,
"Content-Type": "application/json",
};
}
async function callMercury(method, params) {
const body = JSON.stringify({ jsonrpc: "2.0", id: crypto.randomUUID(), method, params });
const res = await fetch(`${MERCURY_BASE_URL}/v1/mcp`, {
method: "POST",
headers: signedHeaders("POST", "/v1/mcp", body),
body,
});
const { result, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
return result;
}
const server = new Server({ name: "mercury-bridge", version: "1.0.0" }, { capabilities: { tools: {} } });
server.setRequestHandler("tools/list", async () => callMercury("tools/list", {}));
server.setRequestHandler("tools/call", async (request) =>
callMercury("tools/call", request.params),
);
await server.connect(new StdioServerTransport());Worked example: listTrips
listTripsOnce Claude connects, ask it something that needs the traveler's trips (for
example, "what's my next trip?"). Claude issues a tools/call for
listTrips, which your bridge forwards as this signed HTTP request:
POST /v1/mcp HTTP/1.1
Bizaway-Consumer: acme-travel
Bizaway-Timestamp: 1767200400
Bizaway-Signature: <hex hmac-sha256, per the algorithm above>
Authorization: Bearer <traveler-jwt>
Content-Type: application/json
{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"listTrips","input":{}}}Mercury replies with the traveler's trips grouped by temporal bucket:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"result": {
"past": [],
"current": [],
"upcoming": [{ "tripId": "…", "title": "Milan → London" }],
"incomplete": [],
"totalCount": 1
}
}
}Your bridge hands that result.result payload back to Claude over stdio,
which folds it into its reply.
Coming later: native remote-connector support
Claude Desktop's and the Claude API's native remote-MCP-connector fields
expect Streamable HTTP transport plus a connector-driven OAuth handshake,
neither of which /v1/mcp speaks yet. Once Mercury's MCP server adds
Streamable HTTP transport and an OAuth 2.1 authorization server, Claude will
be able to connect directly by URL — no local bridge, no hand-computed
signature. For the Claude API's mcp_servers connector, the shape will look
like:
response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "What's my next trip?"}],
mcp_servers=[
{
"type": "url",
"url": "https://api.example.com/v1/mcp/stream",
"name": "mercury",
"authorization_token": "<oauth-access-token>",
}
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "mercury"}],
betas=["mcp-client-2025-11-20"],
)This guide will be updated with the confirmed connector configuration (and
the equivalent Claude Desktop remote-connector setup) once the transport and
auth surfaces are live; until then, use the local-server path above.
Troubleshooting
-32010(auth error) — the bearer token is missing, malformed, or
expired; re-mint it and confirm your bridge reads the current value.-32001(tool not found) — the tool name is misspelled, or the
account calling in does not have that tool's module/feature flag enabled.
Calltools/listand use the returned names verbatim.-32002(permission denied) — the credential used cannot reach that
tool (for example, a mutating or traveler-scoped tool called with the
service token instead of a traveler JWT).-32012(rate limited) — back off for theretryAfterseconds in the
error'sdatafield before retrying.CONSUMER_AUTH_REQUIRED(HTTP 401, before any JSON-RPC body is read) —
the consumer HMAC headers are missing or the signature does not match; see
Authentication § Troubleshooting.
Reporting issues
This page is maintained by the BizAway integration team. To request changes or
report an error, contact your BizAway integration contact. Edits made directly
in the ReadMe dashboard are overwritten on the next publish.
Updated about 3 hours ago