In the evolving landscape of AI tools, combining OpenAI’s agent capabilities with Anthropic’s Message Control Protocol (MCP) opens up powerful new possibilities. This integration enables developers to create sophisticated applications that leverage both platforms’ strengths. In this article, I’ll walk through how to build this integration using Python, explaining each component in detail.
The Core Integration: Creating an Agent with MCP Tools
Let’s start by examining the main code that creates an OpenAI Agent SDK connected to a WhastApp MCP sever:
async def run_agent() -> None:
async with mcp_tools(
command="npx",
args=[
"wweb-mcp",
"-m", "mcp",
"-s", "local",
"-c", "api",
"-t", "command",
"--api-base-url", "http://localhost:3001/api",
"--api-key", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
],
) as whatsapp_tools:
agent = Agent(
name="whatsapp_agent",
tools=[*whatsapp_tools],
model="gpt-4o-mini",
)
result = await Runner.run(agent, "Can you find the phone number of Isabela?")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(run_agent())
This code snippet demonstrates the fundamental integration between OpenAI’s Agent SDK and an MCP server. Let’s break down what’s happening:
- We create an asynchronous function run_agent() that orchestrates the integration.
- The mcp_tools context manager establishes a connection to the MCP server (in this case, a WhatsApp integration).
- Inside the context, we create an OpenAI Agent with a specific name and model, and provide it with the tools obtained from the MCP server.
- We use the OpenAI Runner to execute the agent with a simple query.
- Finally, we print the result and wrap everything in an asyncio execution.
The magic happens within the mcp_tools function, which converts the MCP server's capabilities into tools that the OpenAI agent can use. Let's examine this critical component in detail.
Understanding the mcp_tools Function
The mcp_tools function serves as the bridge between the OpenAI Agents SDK and Anthropic's MCP server. Here's a detailed breakdown of each part:
Establishing the Connection
The function first sets up the server parameters, which define how to start and communicate with the MCP server.
server_params = StdioServerParameters(
command=command,
args=args,
env=env,
)
It then creates a client connection using stdio_client, which provides read and write streams for communication. Using these streams, it establishes a client session that will handle all communication with the MCP server:
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
Discovering and Converting Tools
After initializing the connection, mcp_tools requests a list of all available tools from the MCP server:
# List available tools
tools = (await session.list_tools()).tools
This is where the function’s core purpose comes into play. For each tool provided by the MCP server, it creates a corresponding FunctionTool that the OpenAI agent can use:
yield [
FunctionTool(
name=tool.name,
description=tool.description,
params_json_schema=tool.inputSchema,
on_invoke_tool=create_invoke_tool(tool),
)
for tool in tools
]
Each FunctionTool includes:
- The original tool’s name
- The tool’s description
- A JSON schema defining the tool’s input parameters
- A function that will be called when the OpenAI agent invokes this tool
The Tool Invocation Handler
The create_invoke_tool function creates a handler that is called whenever the OpenAI SDK agent wants to use a specific MCP tool:
def create_invoke_tool(tool: Tool) -> Callable[[RunContextWrapper[Any], str], Awaitable[str]]:
async def on_invoke_tool(context: RunContextWrapper[Any], args: str) -> str:
if confirmation_fn:
if not await confirmation_fn(tool.name, args):
return "Action not allowed by user"
result = await session.call_tool(tool.name, json.loads(args))
if result.isError:
raise Exception(result.content[0].text)
return result.content[0].text
return on_invoke_tool
This handler performs several important tasks:
- If a confirmation function was provided, it calls that function to ask for permission to execute the tool.
- It calls the MCP tool with the provided arguments.
- It handles any errors that occur during tool execution.
- It returns the tool’s output as a string, which is passed back to the OpenAI agent.
Practical Application: Building a WhatsApp Assistant
In our example, we’ve created a WhatsApp assistant that can search for contact information. When the agent receives the query “Can you find the phone number of Isabela?”, it will:
- Analyze the request to understand what’s being asked
- Identify which MCP tool can handle contact searches
- Invoke that tool with the name “Isabela” as a parameter
- Process the result and return it to the user
This creates a seamless experience where users can interact with the agent using natural language, and the agent can leverage the capabilities of both the OpenAI model and the MCP-connected service.
Here is the complete solution:
import asyncio
import json
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List
from mcp import ClientSession, StdioServerParameters, Tool, stdio_client
from agents import Agent, FunctionTool, RunContextWrapper, Runner
@asynccontextmanager
async def mcp_tools(
command: str,
args: List[str],
env: Dict[str, str] | None = None,
confirmation_fn: Callable[[str, str], Awaitable[bool]] | None = None,
) -> AsyncGenerator[List[FunctionTool], None]:
server_params = StdioServerParameters(
command=command,
args=args,
env=env,
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = (await session.list_tools()).tools
def create_invoke_tool(tool: Tool) -> Callable[[RunContextWrapper[Any], str], Awaitable[str]]:
async def on_invoke_tool(context: RunContextWrapper[Any], args: str) -> str:
if confirmation_fn:
if not await confirmation_fn(tool.name, args):
return "Action not allowed by user"
result = await session.call_tool(tool.name, json.loads(args))
if result.isError:
raise Exception(result.content[0].text) # type: ignore
return result.content[0].text # type: ignore
return on_invoke_tool
yield [
FunctionTool(
name=tool.name,
description=tool.description,
params_json_schema=tool.inputSchema,
on_invoke_tool=create_invoke_tool(tool),
)
for tool in tools
]
async def confirm_action(tool_name: str, args: str) -> bool:
print(f"The agent wants to use {tool_name} with arguments {args}")
response = input("Allow? (y/n): ")
return response.lower() == 'y'
async def run_agent() -> None:
async with mcp_tools(
command="wweb-mcp",
args=[
"-m", "mcp",
"-s", "local",
"-c", "api",
"-t", "command",
"--api-base-url", "http://localhost:3001/api",
"--api-key", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
],
confirmation_fn=confirm_action,
) as whatsapp_tools:
agent = Agent(
name="whatsapp_agent",
tools=[*whatsapp_tools],
model="gpt-4o-mini",
)
result = await Runner.run(agent, "Can you find the phone number of Isabela?")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(run_agent())
Extending the Integration
This basic integration can be extended in several ways:
Adding User Confirmation
You could implement a confirmation function that asks the user before executing potentially sensitive actions:
async def confirm_action(tool_name: str, args: str) -> bool:
print(f"The agent wants to use {tool_name} with arguments {args}")
response = input("Allow? (y/n): ")
return response.lower() == 'y'
async def run_agent() -> None:
async with mcp_tools(
command="wweb-mcp",
args=[...],
confirmation_fn=confirm_action,
) as whatsapp_tools:
# Rest of the code remains the same
Supporting Multiple MCP Services
You could connect to multiple MCP services and combine their tools:
async def run_multi_service_agent() -> None:
async with mcp_tools(command="wweb-mcp", args=[...]) as whatsapp_tools:
async with mcp_tools(command="email-mcp", args=[...]) as email_tools:
agent = Agent(
name="communication_agent",
tools=[*whatsapp_tools, *email_tools],
model="gpt-4o",
)
# Rest of the code
Conclusion
The integration between OpenAI’s Agent SDK and Anthropic’s MCP opens up powerful possibilities for creating AI assistants that can interact with a wide range of services. By understanding the core components of this integration — particularly the mcp_tools function that bridges the gap between these platforms—developers can build sophisticated applications that leverage the best of both worlds.
This approach allows AI agents to access external tools and services while maintaining a simple, unified interface for users. As both OpenAI and Anthropic continue to evolve their platforms, we can expect even more powerful integration possibilities in the future.