
Build Multi-Tool AI Agents with OpenAI Agents SDK and MCP
How to Build Multi Tool AI Agents
A multi-tool agent needs a framework to manage its logic and a standard way to plug in tools. You use the OpenAI Agents SDK to manage agent logic and conversation state. Then, you use the Model Context Protocol (MCP) to connect external databases, APIs, and files safely. Pairing the OpenAI Agents SDK with MCP keeps things clean and reliable. You get native agent coordination and a universal standard for connecting data sources.
A single agent forced to juggle many unrelated tools tends to get brittle under production load. To prevent this, you should build multi-tool AI agents with a clean architecture. This setup keeps your codebase modular and easy to maintain. It also reduces runtime errors and API latency.
Before these standards, developers wrote custom API clients for every tool. They also wrote manual formatting loops, which made maintenance painful and runtime errors common. This guide shows how these two tools fit together. You will learn to build responsive, multi-tool systems without the usual technical headaches.
Understanding the OpenAI Agents SDK and MCP Ecosystem
The OpenAI Agents SDK is a modular Python framework designed for managing agent behavior. It handles streaming outputs, sets up safety guardrails, and executes tool calls. Instead of writing custom loops, you can define clear instructions. The SDK manages handoffs between specialized agents and tracks execution states out of the box.
Meanwhile, the Model Context Protocol (MCP) is a universal bridge. Many developers compare it to a USB-C port for AI applications. It standardizes how external services, databases, file systems, and internal APIs expose their capabilities to language models. This standard decouples the development of tools from the development of the client application. You can learn more about this standard on the official Model Context Protocol website.
Are you currently writing custom API connectors for every new tool you add? How does standardizing your data layer change your development timeline?
When paired together, the Agents SDK handles the reasoning and agent coordination. MCP supplies clean, structured context from external servers. You can explore more architectural details in the OpenAI Agents SDK and MCP Guide.
What is the primary difference between OpenAI's Agents SDK and Model Context Protocol?
The OpenAI Agents SDK is a Python framework for managing agent behavior and handoffs, while MCP is a standardized protocol that connects AI models to external tools and data. The SDK coordinates workflows, while MCP provides the connection layer to external databases and APIs.
Why is standardizing the data layer important for AI agents?
Standardizing your data layer with MCP prevents you from writing custom API connectors for every tool. This reduces development time and maintenance overhead. It allows you to reuse the same server configuration across multiple agents and platforms without rewriting code.
Setting Up Your Development Environment and Core Dependencies
First, you install the required Python packages. You need a fast, reliable package manager to handle async operations. This is key when using local stdio servers or remote endpoints.
When setting up your project, keep your configuration clean. Separate agent definitions from your tool transport layers. This ensures that if an external database or MCP server goes down, your main application logic remains unaffected.
What package manager do you prefer for your Python projects? Have you tested asynchronous streaming for your tool outputs yet?
I recommend using uv for managing your Python environment because it is fast and handles dependencies reliably. You can initialize your project and install the core packages with these commands:
# Initialize a new project
uv init mcp-agent
cd mcp-agent
# Add the OpenAI Agents SDK and dotenv
uv add openai-agents python-dotenv
Once your environment is ready, you can create a clean script that sets up a local stdio server connection. For a deeper dive into setting up streamable HTTP transports and handling local subprocesses, check out the official MCP servers repository.
How do I install the OpenAI Agents SDK with MCP support?
You can install the SDK by running uv add openai-agents in your Python project. This package includes the core agent framework and MCP transport utilities. It allows you to import both the agent runner and the standard MCP server classes directly.
Coordinating Multi Agent Workflows and Handoffs
Complex tasks rarely belong to a single general-purpose assistant. By using agent handoffs within the OpenAI Agents SDK, you can delegate specific tasks to specialized sub-agents. For example, you can route a support query to a billing agent or hand code review tasks to a technical checker.
This modular approach keeps your system prompts focused and prevents context bloat. Each agent only sees the tools and instructions relevant to its immediate responsibility. This results in faster response times and more predictable behavior. You can read more about managing multi-agent workflows in parallel to see how this works at scale.
How do you currently split responsibilities across your AI workflows? What happens when a sub-agent needs to pass context back to the primary coordinator?
To see how this works in production, imagine a customer support agent. When a user asks about an order, the primary agent queries a database via an MCP database server. If the user then asks to change their shipping address, the primary agent hands off the conversation to a specialized shipping agent. This shipping agent validates the address using an external API, updates the database, and hands control back to the primary coordinator. This multi-agent setup keeps prompts small, reduces token usage, and prevents the agent from executing incorrect tools.
Here is a simple Python example of how to define an agent handoff. In this code, the primary agent can hand off the conversation to a specialized research agent when the user asks for deep research:
import asyncio
from agents import Agent, Runner
# Define the specialized research agent
research_agent = Agent(
name="ResearchAgent",
instructions="You are a research specialist. Conduct deep research on the topic.",
model="gpt-4o"
)
# Define the handoff function
def handoff_to_research():
"""Transfer the conversation to the specialized research agent."""
return research_agent
# Define the primary agent
primary_agent = Agent(
name="PrimaryCoordinator",
instructions="You are the main coordinator. Help the user and delegate research tasks.",
model="gpt-4o",
tools=[handoff_to_research]
)
async def main():
result = await Runner.run(primary_agent, "Please research the latest trends in quantum computing.")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
What are agent handoffs in the OpenAI Agents SDK?
Agent handoffs occur when one agent transfers the conversation control and context to another specialized agent. This keeps prompts simple and focused, which you achieve by defining a tool function that returns another Agent instance, which the runner automatically transitions to.
How does dividing tasks among sub-agents improve performance?
Dividing tasks reduces context bloat because each sub-agent only handles a specific set of tools and instructions. This leads to faster and more accurate responses. It also makes debugging easier since you can isolate and test individual agent behaviors independently.
Comparing SDK Handoffs and Custom Tool Wrapper Scripts
When building multi-tool agents, you have two primary architectural options. You can use the native handoff features in the OpenAI Agents SDK. Alternatively, you can write custom wrapper scripts to manage tools manually. This choice is similar to choosing between deterministic databases and autonomous workflows for your backend services.
Writing custom wrappers often feels easier at first. However, it quickly becomes difficult to maintain. These are the differences to understand why native handoffs are more reliable.
- Development overhead
- SDK handoffs: Low. You define tools that return another agent, and the SDK manages the transition.
- Custom wrappers: High. You must write custom loops, handle JSON parsing, and manually manage conversation state.
- Prompt complexity
- SDK handoffs: Minimal. Each specialized agent only needs a short, focused system prompt.
- Custom wrappers: High. A single general-purpose agent needs a massive prompt to handle all possible tools.
- Error handling
- SDK handoffs: Standardized. The SDK includes built-in retry logic and guardrails for tool execution.
- Custom wrappers: Brittle. You must write custom try-except blocks for every API call and handle rate limits manually.
- Context management
- SDK handoffs: Automatic. The runner passes the conversational history and state to the sub-agent.
- Custom wrappers: Manual. You must extract, format, and pass the context between different API calls yourself.
By using native handoffs, you keep your codebase clean and modular. This makes it much easier to scale your workflows as you add more tools and sub-agents.
Connecting External Data Sources Safely via MCP Servers
Connecting external APIs and internal databases to your AI agents introduces security and authentication considerations. MCP uses a standard message format, so any tool can talk to any agent without custom code. This allows you to enforce access controls and manage least-privilege credentials. You can also require human approval before sensitive actions execute.
You can connect via local stdio processes, server-sent events (SSE), or streamable HTTP endpoints. Keep your authorization headers secure to protect your data.
What security protocols do you currently enforce for your internal database connections? How do you handle approval flows for destructive tool actions?
Use strict schema validation and tool filters. This restricts agents to the specific endpoints and files they need. Here is how to connect a local file system MCP server securely using the python SDK:
import asyncio
import os
from dotenv import load_dotenv
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
load_dotenv()
async def main():
# Define parameters for the filesystem MCP server
server_params = {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./sandbox"]
}
# Establish a secure connection to the local stdio server
async with MCPServerStdio(params=server_params) as mcp_server:
agent = Agent(
name="SecureFileAssistant",
instructions="Use the files in the sandbox directory to answer questions.",
model="gpt-4o",
mcp_servers=[mcp_server]
)
result = await Runner.run(agent, "List the files in the sandbox directory.")
print(result.final_output)
In this code, the agent is restricted to the ./sandbox folder. This is a simple but effective way to enforce safe boundaries for your AI tools. You can learn more about configuring secure tool access in the official MCP servers repository.
How do I secure tool connections when using MCP servers?
You can secure connections by using authorization headers, implementing least-privilege credentials, and configuring approval policies that require human confirmation for sensitive actions. The OpenAI Agents SDK allows you to define custom approval callbacks for specific tool executions.
What transport options are supported for MCP servers in Python?
The Python SDK supports local stdio subprocesses, streamable HTTP servers, Server-Sent Events (SSE) servers, and hosted MCP tools. Stdio is ideal for local development and secure sandbox environments, while HTTP and SSE are suited for remote APIs.
Common Pitfalls When Combining OpenAI SDK and MCP
Integrating these two frameworks is powerful. However, developers often make a few common mistakes. Recognizing these challenges early will save you hours of debugging.
These are the most frequent pitfalls and how to avoid them in your projects.
- Infinite loop execution
- Problem: An agent gets stuck in an infinite loop of calling the same tool repeatedly without making progress.
- Fix: Set a maximum execution step limit in your runner settings. This forces the agent to stop if it exceeds a certain number of calls.
- Missing environment variables in subprocesses
- Problem: The MCP server fails to start because it cannot access required API keys or environment variables.
- Fix: Ensure you pass environment variables explicitly to the server parameters dictionary. You can also use
dotenvto load them before starting the server. - Context window exhaustion
- Problem: Passing large files or database schemas through MCP quickly fills up the agent's context window.
- Fix: Implement pagination or vector search to retrieve only the most relevant chunks. Avoid loading entire databases at once.
- Slow response times with nested handoffs
- Problem: Having too many sub-agents passing tasks back and forth causes noticeable latency.
- Fix: Design a flat routing structure where the primary agent delegates directly to sub-agents. Avoid nesting them multiple levels deep.
By addressing these issues during the design phase, you can build a stable and responsive automation system.
Putting It Together with an End to End Script
Now let's look at a complete, runnable script. This script combines a primary coordinator agent, a specialized research sub-agent, and a local filesystem MCP server.
This end-to-end setup demonstrates how the OpenAI Agents SDK manages handoffs while MCP provides secure tool access.
import asyncio
import os
from dotenv import load_dotenv
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
load_dotenv()
# Define the specialized research agent
research_agent = Agent(
name="ResearchAgent",
instructions="You are a research specialist. Conduct deep research and save findings.",
model="gpt-4o"
)
# Define the handoff function
def handoff_to_research():
"""Transfer the conversation to the specialized research agent."""
return research_agent
async def main():
# Define parameters for the filesystem MCP server
server_params = {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./sandbox"]
}
# Establish a secure connection to the local stdio server
async with MCPServerStdio(params=server_params) as mcp_server:
# Define the primary coordinator agent with access to tools and MCP
primary_agent = Agent(
name="PrimaryCoordinator",
instructions="Help the user. Use the research agent for deep research tasks.",
model="gpt-4o",
tools=[handoff_to_research],
mcp_servers=[mcp_server]
)
# Run the agent workflow
result = await Runner.run(
primary_agent,
"Please research quantum computing and list the files in our sandbox."
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
This script shows how clean your code remains when you use these standards. The SDK manages the conversational state and handles the transition between agents. Meanwhile, MCP provides a secure, isolated channel to interact with your local file system. This architecture makes it simple to add more tools or specialized agents as your automation needs grow.

Owais Abdullah
Web & AI Engineer · Founder @ Octively
Spec-driven developer and AI engineer. Founder of Octively, building Next.js SaaS platforms, autonomous Digital FTEs (AI employees), and production-ready intelligent workflows.
Did you find this article helpful?



