Model Context Protocol (MCP) Servers with the OpenAI Agents
“Think of MCP as USB-C for your language model.”
Agents are most useful when they can do things: read files, hit an API, crawl the web… The Model Context Protocol (MCP) was designed to make that easy and consistent. Any service that speaks MCP exposes its capabilities as tools, and the Agents SDK lets your LLM call those tools without extra glue code.
In this tutorial we’ll:
1. Spin up two ready-made MCP servers
• the official filesystem server
• Firecrawl, a powerful web-scraping & research server
2. Wire them into an Agent
3. Run a demo that scrapes truepixai.com and drops the findings into a local research.txt file.
More ready-made Server can be be found here
To Learn How to build your own MCP sever visit here
1. Why MCP?
- Standard vocabulary — every tool describes its name & arguments the same way.
- Any transport — local subprocess (stdio) or remote SSE/HTTP.
- Hot-swap — add, remove, or update servers without touching agent logic.
- Autodiscovery — the SDK calls list_tools() each run, so the LLM always knows which tools are available.
2. Prerequisites
pip install openai-agents export FIRECRAWL_API_KEY=fc-XXXXXXXXXXXXXXXX
export OPENAI_API_KEY= XXXXXXXXXXXXXXXX3. The code
# mcp_agent.py
"""
Filesystem + Firecrawl MCP Agent
================================
A minimal yet extensible Agent that exposes **your chosen local directory** and
Firecrawl web‑scraping tools via the Model Context Protocol (MCP).
Usage examples
~~~~~~~~~~~~~~
```
# 1) Explicit path flag
python mcp_agent.py --fs-root /path/to/content --headless
# 2) Environment variable
export FILESYSTEM_ROOT_DIR=/path/to/content
python mcp_agent.py
# 3) Defaults to project root/sample_files (auto‑created)
python mcp_agent.py
```
The `--headless` flag suppresses trace URLs and banner prints – handy for CI.
"""
from __future__ import annotations
import argparse
import asyncio
import os
import shutil
import sys
from contextlib import AsyncExitStack
from pathlib import Path
from typing import List
from agents import Agent, Runner, gen_trace_id, trace
from agents.mcp import MCPServer, MCPServerStdio
# ──────────────────────────────────────────────────────────────────────────────
# Environment + CLI parsing
# ──────────────────────────────────────────────────────────────────────────────
DEFAULT_ROOT = (
Path(os.getenv("FILESYSTEM_ROOT_DIR", ""))
if os.getenv("FILESYSTEM_ROOT_DIR")
else Path(__file__).resolve().parent / "sample_files"
)
def parse_args() -> argparse.Namespace: # noqa: D401 – simple helper
"""Parse command‑line flags."""
parser = argparse.ArgumentParser(description="Filesystem + Firecrawl Agent")
parser.add_argument(
"--fs-root",
type=Path,
help="Directory the filesystem MCP server will expose.",
)
parser.add_argument(
"--headless",
action="store_true",
help="Hide banner and trace URL (useful for automated runs).",
)
return parser.parse_args()
ARGS = parse_args()
FS_ROOT: Path = (ARGS.fs_root or DEFAULT_ROOT).expanduser().resolve()
# ──────────────────────────────────────────────────────────────────────────────
# MCP‑server factory helpers
# ──────────────────────────────────────────────────────────────────────────────
def ensure_npx() -> None:
if not shutil.which("npx"):
sys.exit(" npx is required but not found in PATH. Install Node + npm.")
def filesystem_server(root_dir: Path, *, cache: bool = True) -> MCPServerStdio:
"""Create a filesystem MCP server rooted at *root_dir* (auto‑created)."""
ensure_npx()
# Make sure the directory exists so the Node server can't throw ENOENT.
try:
root_dir.mkdir(parents=True, exist_ok=True)
except PermissionError as e:
sys.exit(f" Cannot create/access directory {root_dir}: {e}")
return MCPServerStdio(
name="filesystem-mcp",
params={
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(root_dir)],
},
cache_tools_list=cache,
)
def firecrawl_server(api_key: str | None) -> MCPServerStdio:
"""Return a Firecrawl MCP server.
"""
ensure_npx()
api_key = api_key or os.getenv("FIRECRAWL_API_KEY")
if not api_key:
sys.exit(" FIRECRAWL_API_KEY not provided (env or arg).")
return MCPServerStdio(
name="firecrawl-mcp",
params={
"command": "npx",
"args": ["-y", "firecrawl-mcp"],
"env": {"FIRECRAWL_API_KEY": api_key,
"FIRECRAWL_RETRY_MAX_ATTEMPTS": "5",
"FIRECRAWL_RETRY_INITIAL_DELAY": "5000",
"FIRECRAWL_RETRY_MAX_DELAY": "5000"
},
},
)
# ──────────────────────────────────────────────────────────────────────────────
# Server registry
# ──────────────────────────────────────────────────────────────────────────────
def load_mcp_servers() -> List[MCPServer]:
"""Register all MCP servers here."""
return [
filesystem_server(FS_ROOT),
firecrawl_server(None), # API key pulled from env by default
]
# ──────────────────────────────────────────────────────────────────────────────
# Runner helpers
# ──────────────────────────────────────────────────────────────────────────────
async def run_agent(agent: Agent, *messages: str) -> None: # noqa: D401
for msg in messages:
print(f"\n🟡 {msg}")
result = await Runner.run(starting_agent=agent, input=msg)
print(f"🟢 {result.final_output}")
async def main() -> None: # noqa: D401
async with AsyncExitStack() as stack:
servers = [await stack.enter_async_context(s) for s in load_mcp_servers()]
agent = Agent(
name="Assistant",
instructions=(
"You can read from the configured local filesystem and use "
"Firecrawl web tools. Employ whichever suite helps you solve "
"the user's request."
),
mcp_servers=servers,
)
trace_id = gen_trace_id()
with trace(workflow_name="Filesystem + Firecrawl Agent", trace_id=trace_id):
if not ARGS.headless:
print("Trace URL:", f"https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
# Demo messages – modify or remove as you wish
await run_agent(
agent,
"List all files in the current directory.",
"Scrape truepixai.com and then create a research.txt file detailing what you learned about Truepix in simple terms.",
)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nInterrupted by user.")Save the script, then run:
python mcp_agent.py ~/my_agent_workspace- The filesystem server exposes ~/my_agent_workspace — create or read any file there.
- Firecrawl handles firecrawl_deep_research, firecrawl_scrape, and friends.
When the agent receives our demo prompt, it will:
- Ask Firecrawl’s tools for an overview of truepixai.com
- Summarize the findings
- Call the filesystem tool write_file() to save research.txt in your workspace.
Open the file and you’ll see a neatly formatted report about Truepix AI .
4. Wrap-up
With barely a page of Python we plugged two powerful capabilities into our LLM:
- local file I/O
- full-stack web research
That’s the magic of the Model Context Protocol: clear contracts, zero glue. From here, try swapping Firecrawl for a database MCP server, or chain multiple tools in a single prompt. The agent won’t blink.
Did this guide help? Feel free to share, clap, or drop a comment. See you in the next build!
