MCP Deep Dive, Part 14: Wiring MCP Into OpenAI and Agent Frameworks — One Server, Any Model
The MCP servers you built aren't tied to one model. Here's wiring MCP into OpenAI (client-side + the hosted tool) and agent frameworks — one server, any model.
- Author
- Randhir Jassal
- Published
- Reading time
- 10 min read
- Views
- 11 views
We built the Mattrx MCP servers in C# and ran them on Azure — but here's the quiet superpower we've been building toward the whole series: none of that ties them to a single model. The same
mattrx-analyticsserver can be driven by OpenAI, by Anthropic, by Gemini, or by any agent framework — because a tool declared in MCP is a tool declared for everyone. This part wires it up.
This is Part 14 of a 15-part deep dive on Model Context Protocol (MCP). It's also the part that pays off Part 12's Python-vs-.NET point: the servers stay .NET, but the client driving them is usually Python (the OpenAI SDK's home turf). We'll wire MCP into OpenAI two ways, plug it into agent frameworks, and — most importantly — keep the governance from Parts 6–8 intact when a platform you don't control starts calling your tools.
TL;DR
| Concern | Per-provider wiring (before) | MCP as the tool layer (after) |
|---|---|---|
| Tool schemas | re-declared per provider | one MCP server, translated |
| OpenAI (client-side) | hand-written functions | discover → translate → loop |
| OpenAI (hosted) | not possible | Responses API mcp tool |
| Frameworks | per-framework adapters | pass MCP servers in |
| Model swap | rewrite the tool layer | change the client, not the server |
| Governance (hosted) | — | enforced at the server |
- MCP tool defs are JSON Schema; OpenAI tool-calling consumes JSON Schema → a thin translation (Part 4), for any provider.
- Client-side: discover MCP tools, translate to OpenAI's
tools, run the loop yourself — full control, Chat Completions. - Hosted: OpenAI's Responses API
mcptool connects to your server and calls tools server-side — least code, but OpenAI's infra reaches your server. - Agent frameworks speak MCP natively — the OpenAI Agents SDK takes
mcp_servers, Semantic Kernel imports MCP tools (nice for .NET), LangGraph has adapters. - With the hosted tool, push governance to the SERVER: least-privilege token (Part 7), tool allow-list, approval gates, injection/exfil defenses (Part 8).
- One server, any model: swapping OpenAI ↔ Anthropic ↔ Gemini is a client change; servers, auth, and scopes are untouched.
- The N×M → N+M win from Part 1, now applied to models, not backends.
- The client/host is usually Python here; the servers stay .NET — the split from Part 12.
- A model swap becomes an experiment, not a migration.
- The convenience of the hosted tool is fine; trusting it is not — govern at your boundary.
The one mental shift: the MCP servers you built are model-agnostic by design. OpenAI can drive them client-side (you run the loop) or hosted (OpenAI reaches your server), and so can any framework. The strategic win is that the model becomes a swappable part — you own the tools; the provider is just a client.
The running example: driving Mattrx's servers with OpenAI
Mattrx's Python FastAPI AI service is an MCP client (Part 4). Its job is to drive the three C# servers with whatever model is best for a task. This part shows it wiring those servers into OpenAI — the client-side loop and the hosted tool — then generalizes to frameworks, all against the same unchanged servers.
The two integration modes
MODE A — client-side (you run the loop)
Your host: MCP client <-> MCP server
|
+--> OpenAI Chat Completions (translate tools, run the loop)
You control: the loop, the reach to your server, and the governance (your gateway).
MODE B — hosted (OpenAI runs the loop)
OpenAI Responses API (mcp tool) ----> your MCP server (public + auth)
OpenAI's platform reaches your server directly.
You control: only what's enforced AT the server (auth, scopes, allow-list, approval).
1. MCP as the universal tool layer
Before
You hand-wrote tool schemas for each provider — OpenAI functions here, Anthropic tools there, Gemini declarations elsewhere. N providers × M tools, all drifting apart.
# BEFORE: re-declare every tool for every provider's function-calling format.
openai_tools = [{"name": "get_campaign_kpis", "parameters": {...}}] # hand-written JSON Schema
anthropic_tools = [{"name": "get_campaign_kpis", "input_schema": {...}}] # again, slightly different
After
One MCP server declares the tools once; any provider consumes the same discovered JSON Schema.
# AFTER: one source of truth. Discover once; translate per provider.
mcp_tools = await mcp.list_tools() # the MCP server is the single tool contract
Diagnostic: this is Part 1's N×M → N+M, applied to models instead of backends. Without MCP you re-declare every tool for every provider's format; with MCP the server declares tools once, and each provider is a thin translation of the same JSON Schema.
2. Client-side: MCP tools → OpenAI tool-calling
Before
You maintained a separate set of OpenAI function schemas by hand, drifting from the actual tools.
After
Discover the MCP tools, translate them to OpenAI's tools parameter (a near-identity mapping — both sides are JSON Schema), and run the standard tool-calling loop from Part 4.
# Discover MCP tools and translate to OpenAI's tools param — both are JSON Schema.
mcp_tools = await mcp.list_tools()
openai_tools = [{
"type": "function",
"function": {"name": t.name, "description": t.description, "parameters": t.input_schema},
} for t in mcp_tools]
# The agent loop (Part 4): the model asks for tool calls; you execute them against MCP.
while True:
resp = client.chat.completions.create(model="gpt-...", messages=messages, tools=openai_tools)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
for call in msg.tool_calls:
result = await mcp.call_tool(call.function.name, json.loads(call.function.arguments))
messages.append({"role": "tool", "tool_call_id": call.id, "content": result.text})
Diagnostic: the translation is thin because MCP tool definitions are JSON Schema and OpenAI's parameters field is JSON Schema. You run the MCP client, translate once per session, and drive the tool loop yourself — full control over the loop, the reach to your server, and the governance (your host's gateway sits right here).
Mattrx metric: the Python AI service drives the three servers this way with full control — the same governed gateway (Parts 6–8) wraps every model call, because the loop runs inside our host.
3. Hosted: the OpenAI Responses API mcp tool
Before
There was no way to hand a remote MCP server to the model platform — you always ran the client and the loop.
After
Point the Responses API's hosted mcp tool at your server URL; OpenAI's platform connects to it and calls tools during the model's turn — you write almost no loop code.
# Hosted: OpenAI connects to your MCP server and calls its tools server-side. (APIs evolve — check docs.)
resp = client.responses.create(
model="gpt-...",
input="Why did campaign 4821's CTR drop last week?",
tools=[{
"type": "mcp",
"server_label": "mattrx-analytics",
"server_url": "https://mcp.mattrx.internal/analytics/mcp",
"authorization": f"Bearer {token}", # your Entra token (Part 6)
"require_approval": "never",
}],
)
Diagnostic: the hosted tool is the least-code path — but notice what changed: OpenAI's infrastructure now reaches your server directly. Your server must be publicly reachable, and — critically — your auth (Part 6) and governance (Parts 7/8) now have to hold against a caller you don't run. It's convenience for a trust trade-off, and section 5 is how you make that trade safely.
4. Agent frameworks speak MCP too
Before
Each framework had its own tool-registration format; adopting a new one meant re-wiring your tools.
After
Point the framework at the MCP server. The OpenAI Agents SDK takes mcp_servers; Semantic Kernel (Microsoft, great for .NET shops) imports MCP tools as kernel functions; LangGraph has MCP adapters.
# OpenAI Agents SDK: hand the agent your MCP server; it discovers and drives the tools.
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async with MCPServerStreamableHttp(params={
"url": "https://mcp.mattrx.internal/analytics/mcp",
"headers": {"Authorization": f"Bearer {token}"},
}) as server:
agent = Agent(name="Insights", instructions="...", mcp_servers=[server])
result = await Runner.run(agent, "Why did campaign 4821's CTR drop?")
Diagnostic: every serious agent framework now consumes MCP, so you never wire tools per framework — you point the framework at the server. That's the same discovery-not-hardcoding principle from Part 4, extended to whole frameworks: the MCP server is the contract, the framework is a consumer.
5. Keep governance when a platform drives your tools
Before
With the hosted tool, OpenAI calls your server directly — bypassing the gateway that used to sit inside your host and govern every call.
After
Push the governance down to the server: hand OpenAI a narrow, least-privilege token (Part 7), allow-list the tools it may call, gate sensitive ones behind approval, and keep the injection/exfil defenses (Part 8) at the server boundary.
# The hosted tool means a THIRD PARTY reaches your server. Govern at the SERVER, not the host.
tools=[{
"type": "mcp",
"server_label": "mattrx-analytics",
"server_url": "https://mcp.mattrx.internal/analytics/mcp",
"authorization": f"Bearer {read_only_scoped_token}", # least privilege (Part 7)
"allowed_tools": ["get_campaign_kpis", "query_events"], # restrict the surface
"require_approval": "always", # human-in-the-loop for anything sensitive
}]
Diagnostic: when you hand tools to a hosted platform, the model runtime is no longer inside your host — so your host-side gateway can't govern it. The governance has to live at the server: a read-only, least-privilege token (Part 7), an allow-list of callable tools, approval gates for anything with a side effect, and the tool-result screening and audience-binding from Parts 6 and 8. Treat OpenAI's platform as exactly what it is — an untrusted client — and the convenience is free of risk.
Mattrx metric: when Mattrx exposes a server to a hosted model runtime, it gets a scoped, read-only token and an allow-list — the same least-privilege posture we'd give any external caller (Part 1's "safe door for external AI," now for a model platform).
6. One server, any model
Before
Your tools were re-declared in each vendor's format, so switching providers meant rewriting the tool layer.
After
The server declares tools once in MCP's JSON-Schema form, and any model drives them.
BEFORE: tools re-declared per provider
OpenAI functions + Anthropic tools + Gemini declarations = N x M
AFTER: one MCP server, any model drives it
mattrx-analytics (MCP)
/ | \ \
OpenAI Anthropic Gemini Agents SDK / LangGraph / Semantic Kernel
(all consume the same discovered JSON-Schema tools)
Diagnostic: this is the strategic payoff of the entire series. Because the server declares tools in MCP's provider-neutral JSON Schema, swapping the model is a client/config change — the servers, auth, scopes, and governance never move. You are not locked into a vendor's tool format; you own the tools, and the model is a part you can swap.
Mattrx metric: Mattrx's AI service can drive the same three servers with different providers — so evaluating a new model is an experiment we run in an afternoon, not a migration we schedule. The servers we built in Parts 3 and 12 didn't change one line.
The numbers, in one place
| Aspect | Per-provider wiring (before) | MCP tool layer (after) |
|---|---|---|
| Tool declarations | N providers × M tools | 1 server, translated |
| Add a provider | re-declare every tool | translate the same schemas |
| Model swap | rewrite the tool layer | change the client/config |
| Hosted-tool governance | — | at the server (scoped + allow-list) |
| Framework adoption | per-framework adapters | point it at the MCP server |
| Servers touched on swap | many | zero |
OpenAI + frameworks checklist
- Keep the MCP server as the single tool contract; translate per provider, never re-declare.
- Client-side: discover → translate to
tools→ run the loop (full control + your gateway). - Hosted (
mcptool): hand OpenAI the server URL + a scoped token, not a broad one. - With the hosted tool, allow-list callable tools and gate sensitive ones with approval.
- Keep auth (Part 6), scopes (Part 7), and injection/exfil defenses (Part 8) at the server.
- For frameworks, pass the MCP server in (Agents SDK / Semantic Kernel / LangGraph) — don't re-wire tools.
- Test long/streaming tools (Part 9) through the hosted path — the loop runs elsewhere.
- Design so switching providers is a client change, and prove it with a real swap.
The honest stuff: proportion and pitfalls
- One model, forever. If you'll truly never switch providers, the model-agnostic win is smaller — but you still keep discovery and one governed tool contract.
- A broad token on the hosted tool. Handing OpenAI a wide-scoped token is the confused-deputy risk (Parts 6/8) at platform scale. Scope it down and allow-list.
- Assuming the hosted tool respects your host gateway. It doesn't — OpenAI calls the server directly. Governance must be at the server.
- Approval fatigue.
require_approval: "always"on read tools trains users to click through. Reserve approval for writes and destructive tools. - Streaming through the hosted path. Long tools (Part 9) behave differently when the platform runs the loop. Test the async-job path explicitly.
- Framework lock-in via the adapter. Don't let a framework's MCP adapter re-introduce its own tool format on top. Keep the MCP server as the contract.
- Latency of the hosted hop. OpenAI → your server → back adds round-trips. For tight, latency-sensitive loops, client-side control can be faster.
The model to carry forward
MCP makes the model a swappable part. Your server declares tools once in JSON Schema; OpenAI consumes them client-side or hosted, and every agent framework consumes them too. Own the tools and keep the governance at the server, and switching providers stops being a migration you dread and becomes an experiment you run before lunch. That is the entire point of building on a protocol instead of a vendor.
Three habits that keep MCP model-agnostic:
- Keep the MCP server as the single tool contract. Translate to each provider; never re-declare a tool.
- Govern at the server — especially for hosted tools. Least privilege, allow-list, approval, injection defense; the platform is a client.
- Treat the model as swappable. Design so changing providers is a client/config change, and prove it with a real swap.
In Part 15 we close the series with everything running together: lessons from operating MCP in production at Mattrx — what held, what broke, and what we'd do differently.
Continue the series — MCP Deep Dive
- Why Model Context Protocol Kills Integration Glue Code for Good
- Inside the MCP Architecture: Hosts, Clients, and Servers
- Build a Production-Grade MCP Server From Scratch
- Build an MCP Client That Connects to Any Tool (and Any Model)
- Custom MCP Tools Your AI Agents Can Actually Trust
- MCP Authentication With OAuth and Entra ID, Done Right
- Reaching a Tool Isn't Being Allowed — Least-Privilege Authorization for MCP Agents
- When a Tool Result Is the Attack — Securing MCP Against Prompt Injection and Tool Abuse
- When the Tool Takes Minutes — Streaming and Long-Running Tools Over MCP
- When the Agent Feels Off — Debugging and Observability for MCP in Production
- The Protocol Was Never the Blocker — Rolling MCP Across the Enterprise
- Building MCP Servers in C# and .NET 9 — The SDK, DI, and Native AOT
- Hosting MCP on Azure at Real Scale — Container Apps, Autoscaling, and the SSE Gotcha
- Wiring MCP Into OpenAI and Agent Frameworks — One Server, Any Model (you are here)
- Running MCP in Production — Lessons From Mattrx
Further reading
- MCP Deep Dive, Part 4: Build an MCP Client That Connects to Any Tool (and Any Model)
- MCP Deep Dive, Part 8: When a Tool Result Is the Attack — Securing MCP Against Prompt Injection and Tool Abuse
- MCP Deep Dive, Part 15: Running MCP in Production — Lessons From Mattrx
Wiring MCP into OpenAI or an agent framework and want a second pair of eyes on the client-vs-hosted trade-off? I'm always happy to compare notes — reach me at randhir.jassal@gmail.com.
Get the next issue
A short, curated email with the newest posts and questions.