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:
- 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
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:
- Call
tools/listonce (cache the result) to get each tool'sname,
description, and JSON-SchemainputSchema. - Map each entry to an OpenAI function/tool definition — the schemas are
already JSON Schema, so this is close to a direct pass-through. - Pass those definitions as
toolson your Responses (or Chat Completions)
API call. - When the model emits a function/tool call, forward it to Mercury's
tools/calland 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
listTripsGive 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.
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