The Agents SDK, MCP, and Multi-Agent Patterns
Explore the OpenAI Agents SDK and how it structures agent and runner functions, manages action approvals, and integrates with customer systems through the Model Context Protocol. Learn to handle multi-agent coordination, secure session management, and authorization challenges. Understand best practices for designing scalable, secure AI systems and prepare to discuss these in Forward Deployed Engineer interviews.
We'll cover the following...
At 12:01 UTC, a Northstar operator approved a proposal: create an internal callback task for ticket NS-209 at 13:00, ahead of the ticket’s 14:00 deadline. The approval was valid until 12:10.
The worker processing it restarted. When it came back at 12:11, it found the stored decision, saw approved=True, and created the task.
The callback time was still fine. The approval was not. It had expired a minute earlier, and nobody had checked. The task existed, the receipt was real, and the system had acted on a permission it no longer had.
This is the kind of failure that appears when an agent gets its first real action. This lesson moves Northstar from the previous lesson’s hand-written loop onto the OpenAI Agents SDK, adds an action a person must approve, connects the agent to customer systems through MCP, and asks when a second agent is worth adding. Throughout, one question keeps coming back: which responsibilities does the framework take over, and which stay with us?
What does the Agents SDK take over?
The Agents SDK (pip install openai-agents, imported as agents) splits the previous lesson’s loop into two pieces. An Agent holds instructions, a model, tools, and an optional typed output. The Runner runs the loop: it calls the model, runs tool calls, feeds results back, follows handoffs, and stops at a final output.
The program below gives the agent the previous lesson’s dispatcher as a tool and runs the same comparison for two operators: one who may see both tickets, and one who may only see NS-209. Add your OpenAI API key in the widget before you run it.
Let’s go through the important parts:
Line 9:
set_default_openai_keygives the SDK your API key.Lines 22–27: The context object carries the signed-in organization, entitlements, a call budget, and the reads collected in this run. The SDK passes it to tools but never sends it to the model.
Lines 49–58: The tool keeps the previous lesson’s dispatcher,
read_ticket. The SDK decides when to call it; the tool decides whether the call is allowed.Lines 61–68:
acceptrequires an authorized read of every requested ticket in this run and checks each status against its read.Lines 71–77: The agent holds the instructions, the model, the tool, and the typed output, the previous lesson’s
Comparison.Lines 82 and 89:
max_turns=4raisesMaxTurnsExceededif exceeded. The six-call budget stays in our code, because one turn can request several calls.
Below is the output from one run.
Reads this run: [('NS-104', True), ('NS-209', True)]Priority: NS-209Acceptance: acceptedNarrow operator reads: [('NS-104', False), ('NS-209', True)]Acceptance: ['NS-104: no authorized read in this run']
Acceptance needs one rule that is easy to forget. It must require an authorized read of every requested ticket in this run. Checking only that no read was denied passes a run in which the model made no reads at all and wrote a plausible comparison from nothing.
The SDK Takes Over | The Application Keeps |
Model turns and the turn limit | Authenticated identity |
Invoking function tools | Authorization inside every tool |
Matching results to their calls | Who may open which conversation history |
Handoffs between agents | Budgets that span turns |
Run completion and interruption | Business acceptance of the final output |
Not every component needs the SDK. A step that takes one authorized input and returns one structured result with no history is often clearer as a direct Responses call. Use whichever makes that component’s control flow easiest to inspect. ...