Connect an OpenAI-based client to Mercury's MCP server

Wire Mercury's MCP tools into an OpenAI Assistants/Apps SDK integration or a function-calling client.

Mercury exposes its travel-search, booking, and trip tools as an
MCP server at POST /v1/mcp (the same
assistant tool surface the Mercury app itself
drives). This guide covers two ways to reach it from an OpenAI-based
integration: a function-calling bridge you run yourself (works today),
and OpenAI's hosted MCP connector (depends on prerequisite work landing
on Mercury's side first — see Coming later
below).

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.

Connecting today: a function-calling bridge

Since your own server holds the credentials and makes the HTTP call, this
path works with today's transport and has no dependency on unshipped
server-side work. The shape is:

  1. Call tools/list once (cache the result) to get each tool's name,
    description, and JSON-Schema inputSchema.
  2. Map each entry to an OpenAI function/tool definition — the schemas are
    already JSON Schema, so this is close to a direct pass-through.
  3. Pass those definitions as tools on your Responses (or Chat Completions)
    API call.
  4. When the model emits a function/tool call, forward it to Mercury's
    tools/call and return the result as the tool output.
import hashlib
import hmac
import json
import time
import uuid

import httpx
from openai import OpenAI

MERCURY_BASE_URL = "https://api.example.com"
CONSUMER = "acme-travel"
CONSUMER_SECRET = "<your consumer secret>"
traveler_jwt = "<traveler-jwt>"  # issued by BizAway sign-in

openai_client = OpenAI()
mercury = httpx.Client(base_url=MERCURY_BASE_URL, timeout=15.0)


def _signed_headers(method: str, path: str, body: bytes) -> dict[str, str]:
    timestamp = str(int(time.time()))
    body_hash = hashlib.sha256(body).hexdigest()
    canonical = f"{method}\n{path}\n{timestamp}\n{body_hash}"
    signature = hmac.new(CONSUMER_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
    return {
        "Bizaway-Consumer": CONSUMER,
        "Bizaway-Timestamp": timestamp,
        "Bizaway-Signature": signature,
        "Authorization": f"Bearer {traveler_jwt}",
        "Content-Type": "application/json",
    }


def call_mercury_mcp(method: str, params: dict) -> dict:
    body = {"jsonrpc": "2.0", "id": str(uuid.uuid4()), "method": method, "params": params}
    payload = json.dumps(body).encode()
    response = mercury.post(
        "/v1/mcp", content=payload, headers=_signed_headers("POST", "/v1/mcp", payload)
    )
    data = response.json()
    if "error" in data:
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data["result"]


def mercury_tools_as_openai_functions() -> list[dict]:
    tools = call_mercury_mcp("tools/list", {})["tools"]
    return [
        {
            "type": "function",
            "name": t["name"],
            "description": t["description"],
            "parameters": t["inputSchema"],
        }
        for t in tools
    ]

Worked example: listTrips

Give the model the mapped tool list and let it decide when to call one:

response = openai_client.responses.create(
    model="gpt-4.1",
    input="What's my next trip?",
    tools=mercury_tools_as_openai_functions(),
)

for item in response.output:
    if item.type == "function_call" and item.name == "listTrips":
        tool_result = call_mercury_mcp(
            "tools/call", {"name": "listTrips", "input": {}}
        )
        # tool_result == {"result": {"past": [...], "current": [...],
        #                  "upcoming": [...], "incomplete": [...], "totalCount": N}}
        # Feed tool_result back as the function_call_output for this call_id,
        # then call responses.create again to get the model's final reply.

The signed HTTP request your bridge sends for that call looks exactly like
the one on the Claude integration guide — same JSON-RPC
envelope, same signed headers, same response shape — because both platforms
are calling the same /v1/mcp endpoint; only the client-side tool-call
plumbing differs.

Coming later: OpenAI's hosted MCP connector

OpenAI's hosted MCP tool (Responses API type: "mcp", and the Apps SDK
built on it) calls your MCP server directly from OpenAI's infrastructure
over Streamable HTTP, with a single static Authorization header — which a
per-request signed header can't express, so pointing it at /v1/mcp doesn't
work yet. Once Mercury's MCP server adds Streamable HTTP transport and an
OAuth 2.1 authorization server, you'll be able to point the hosted mcp
tool (or the Apps SDK) directly at Mercury's MCP URL, with OpenAI's servers
calling Mercury and handling the OAuth handshake themselves — no
function-calling bridge required:

response = openai_client.responses.create(
    model="gpt-4.1",
    input="What's my next trip?",
    tools=[
        {
            "type": "mcp",
            "server_label": "mercury",
            "server_url": "https://api.example.com/v1/mcp/stream",
            "authorization": "<oauth-access-token>",
        }
    ],
)

This guide will be updated with the confirmed connector configuration once
that transport and auth surface is live; until then, use the function-calling
bridge above.

Troubleshooting

  • -32010 (auth error) — the bearer token is missing, malformed, or
    expired; re-mint it and retry.
  • -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?