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:

  1. Consumer HMAC — the Bizaway-Consumer / Bizaway-Timestamp /
    Bizaway-Signature headers that identify your integration and are
    recomputed per request from a shared secret. Full algorithm and worked
    examples: Authentication § Consumer HMAC.
  2. Bearer token — an Authorization: Bearer header 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.

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

Once 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.
    Call tools/list and 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 the retryAfter seconds in the
    error's data field 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.


Did this page help you?