Chapter 21
WELCOME TO WEEK 6
WELCOME TO WEEK 6
The Epic Finale Week
And
WELCOME TO THE MODEL CONTEXT PROTOCOL!
And welcome back to OpenAI Agents SDK.
# The imports
from dotenv import load_dotenv
from agents import Agent, Runner, trace
from agents.mcp import MCPServerStdio
from IPython.display import Image, display
import osload_dotenv(override=True)Let's use MCP in OpenAI Agents SDK
MCP lets an agent use tools that someone else wrote and runs as a separate program. We start a small client, it spawns the server as a subprocess, and the server reports back the tools it offers. Once you can list one server's tools, you can use any of them the same way.
We meet three local servers, then hand a couple to an agent. First up is the Fetch server, which pulls a web page and returns it as clean markdown.
async with starts the server and shuts it down when the block ends. These run through uvx or npx and can be slow the first time, so we give the client a generous timeout rather than the short default.
A quick note for Windows
Launching a local MCP server from a notebook hits one rough edge on Windows. The server writes its startup output to stderr, but inside a Windows Jupyter kernel that stream has no real file handle behind it, so the launch fails with an io.UnsupportedOperation: fileno error, while Mac and Linux are unaffected.
The fix is to send the server's stderr to the null device, so it always has somewhere real to write. We do that once in the next cell, which lets every cell below use MCPServerStdio exactly as the OpenAI Agents SDK documents it. On Mac and Linux it costs nothing beyond keeping the server's startup banner out of the notebook.
# On Windows, a stdio MCP server started from a Jupyter kernel writes to a stderr stream with no
# real file descriptor and crashes with io.UnsupportedOperation: fileno. We send the server's
# stderr to the null device so it always has somewhere real to write, which lets every cell below
# use MCPServerStdio exactly as the OpenAI Agents SDK documents it. Mac and Linux are unaffected.
import functools
import subprocess
import agents.mcp.server
agents.mcp.server.stdio_client = functools.partial(agents.mcp.server.stdio_client, errlog=subprocess.DEVNULL)fetch_params = {"command": "uvx", "args": ["--with", "mcp<2","mcp-server-fetch"]}
async with MCPServerStdio(params=fetch_params, client_session_timeout_seconds=60) as server:
fetch_tools = await server.list_tools()
fetch_toolsprint(fetch_tools[0].description)fetch_tools[0].inputSchemaNode and Playwright
We have used a Python MCP server through uvx; the next two run on Node, the JavaScript runtime, through npx. Two quick checks before we start.
First, Node itself; you want v22 or later. If the cell below fails, install Node with one command:
- Windows, in PowerShell:
winget install OpenJS.NodeJS.LTS - Mac, in Terminal:
brew install node - Linux, or if neither works for you: see the guide at setup/SETUP-node.md
After installing, quit Cursor completely and start it again, then reopen this notebook and re-run the lab from the top. That full restart matters: a freshly installed Node is invisible to a notebook that was already running, and restarting just the kernel is not enough.
!node --version
!npx --versionSecond, Playwright, Microsoft's browser automation framework. There is nothing to install: npx fetches it on demand, and it drives the copy of Chrome already on your machine. Chrome does not need to be running; Playwright launches its own.
The cell below proves the whole chain with no AI involved at all: Node runs Playwright, Playwright opens Chrome, loads Hacker News, and saves a screenshot. The first run takes a little longer while npx downloads the package.
If it complains that Chrome is not found, either install Chrome normally or run npx playwright install chrome in a terminal, then try again.
!npx -y playwright@latest screenshot --channel=chrome https://news.ycombinator.com playwright_check.png
display(Image("playwright_check.png"))And now 2 more MCP servers - this time Javascript based, with node
Playwright drives a real web browser, so the agent can open pages and click around. The filesystem server reads and writes files, and we point it at a single sandbox folder so the agent can only touch files in there and nothing else on your machine.
If the next cell hangs, see the important troubleshooting at the end of the SETUP-node instructions linked above.
If the next cell fails on Windows it may be because the command executable path contains spaces.
- Example:
C:\Program Files\nodejs\npx.ps1
Even though we don't write the full path here, the MCP library expands it in the background.
The easiest fix is to run the command through a native shell interpreter whose path is safe: powershell or cmd.
-
Replace:
pythonplaywright_params = {"command": "npx", "args": ["@playwright/mcp@latest"]} -
With:
pythonplaywright_params = {"command": "powershell", "args": ["/c", "npx", "@playwright/mcp@latest"]}
If this works, remember to use the same pattern whenever you build an MCPServerStdioParams object and the command is on a path with spaces:
- Replace the
commandwithpowershellorcmd, and put the original command and its arguments inargsprefixed by"/c".
playwright_params = {"command": "npx", "args": [ "@playwright/mcp@latest"]}
async with MCPServerStdio(params=playwright_params, client_session_timeout_seconds=60) as server:
playwright_tools = await server.list_tools()
playwright_tools
# sandbox_path = os.path.abspath(os.path.join(os.getcwd(), "sandbox"))
# os.makedirs(sandbox_path, exist_ok=True)
# sandbox_path
sandbox_path = os.path.abspath(os.path.join(os.getcwd(), "sandbox")) # the only folder the filesystem server may touch
os.makedirs(sandbox_path, exist_ok=True)
files_params = {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", sandbox_path]}
async with MCPServerStdio(params=files_params, client_session_timeout_seconds=60) as server:
file_tools = await server.list_tools()
file_toolsAnd now, bring on the Agent with Tools!
We hand both servers to a single agent and give it a goal. Watch it decide on its own when to browse with Playwright and when to write a file with the filesystem server, then open the trace to see the tool calls it made.
INSTRUCTIONS = """
You browse the internet to accomplish your instructions.
Accept cookies and navigate pop-ups as needed.
If one website isn't fruitful, try another.
Be persistent until you have solved your assignment,
trying different options and sites as needed.
When you need to write files, you do that inside the sandbox/ folder only.
"""
TASK = "Find a great recipe for Banoffee Pie, then summarize it in markdown to banoffee.md"
async with MCPServerStdio(params=files_params, client_session_timeout_seconds=60) as mcp_server_files:
async with MCPServerStdio(params=playwright_params, client_session_timeout_seconds=60) as mcp_server_browser:
agent = Agent(
name="investigator",
instructions=INSTRUCTIONS,
model="gpt-5.4-mini",
mcp_servers=[mcp_server_files, mcp_server_browser]
)
with trace("investigate"):
result = await Runner.run(agent, TASK, max_turns=20)
print(result.final_output)One more thing: a remote MCP server
Every server so far has been local: we gave MCPServerStdio a command, it spawned the server as a subprocess on your machine, and they talked over stdin and stdout. Plenty of MCP servers instead run as a hosted service you reach over HTTP. You connect to one of those with MCPServerStreamableHttp and a URL, with nothing to spawn and nothing to install.
This is the Context7 teaser from back in Week 2. Context7 is a hosted server that looks up current documentation for libraries, a neat way to hand an agent facts that are newer than its training. We ask a question with no help first, then give the agent Context7 and ask again.
from agents.mcp import MCPServerStreamableHttpquestion = """
In the SandboxAgents feature added to the OpenAI Agents SDK in 2026, what is the Manifest object for?
Be accurate. If you don't know the answer, don't guess. State that you don't know.
"""agent = Agent(name="Expert", instructions="Answer the question.", model="gpt-4o-mini")
result = await Runner.run(agent, question)
print(result.final_output)params = {"url": "https://mcp.context7.com/mcp", "timeout": 60}
async with MCPServerStreamableHttp(name="Context7", params=params) as server:
agent = Agent(name="Expert", instructions="Use Context7 to answer the question.", mcp_servers=[server], model="gpt-4o-mini")
result = await Runner.run(agent, question)
print(result.final_output)