DEV Community

kirandeepjassal-crypto
kirandeepjassal-crypto

Posted on • Originally published at prepstack.co.in

MCP Deep Dive, Part 14: Wiring MCP Into OpenAI and Agent Frameworks — One Server, Any Model

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-analytics server 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 is Part 14 of a 15-part deep dive on Model Context Protocol (MCP). 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

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).
Enter fullscreen mode Exit fullscreen mode

1. MCP as the universal tool layer

Without MCP you hand-write tool schemas for each provider — OpenAI functions here, Anthropic tools there, Gemini declarations elsewhere. N providers × M tools, all drifting apart. With MCP, one server declares the tools once; any provider consumes the same discovered JSON Schema.

# 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 source of truth. Discover once; translate per provider.
mcp_tools = await mcp.list_tools()   # the MCP server is the single tool contract
Enter fullscreen mode Exit fullscreen mode

This is Part 1's N×M → N+M, applied to models instead of backends.

2. Client-side: MCP tools → OpenAI tool-calling

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.

# 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: 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})
Enter fullscreen mode Exit fullscreen mode

The translation is thin because MCP tool definitions are JSON Schema and OpenAI's parameters field is JSON Schema. You run the loop yourself — full control over the loop, the reach to your server, and the governance (your host's gateway sits right here).

3. Hosted: the OpenAI Responses API mcp tool

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",
    }],
)
Enter fullscreen mode Exit fullscreen mode

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 your auth and governance 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

Point the framework at the MCP server. The OpenAI Agents SDK takes mcp_servers; Semantic Kernel (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?")
Enter fullscreen mode Exit fullscreen mode

Every serious agent framework now consumes MCP, so you never wire tools per framework — you point the framework at the server. The MCP server is the contract; the framework is a consumer.

5. Keep governance when a platform drives your tools

With the hosted tool, OpenAI calls your server directly — bypassing the gateway that used to sit inside your host. 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
}]
Enter fullscreen mode Exit fullscreen mode

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, 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.

6. One server, any model

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)
Enter fullscreen mode Exit fullscreen mode

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. Evaluating a new model becomes an experiment you run in an afternoon, not a migration you schedule.

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:

  1. Keep the MCP server as the single tool contract. Translate to each provider; never re-declare a tool.
  2. Govern at the server — especially for hosted tools. Least privilege, allow-list, approval, injection defense; the platform is a client.
  3. Treat the model as swappable. Design so changing providers is a client/config change, and prove it with a real swap.

Originally published at prepstack.co.in. Part 15 closes the series: lessons from operating MCP in production at Mattrx — what held, what broke, and what we'd do differently.

Top comments (0)