FirstOpsFirstOps
Documentation
Sign in

OpenAI Agents SDK Integration#

Govern an OpenAI Agents SDK agent with FirstOps — across the model, your function tools, and MCP servers.

What FirstOps governs#

SurfaceHow it's wiredWhat you get
LLM callspoint the Agents SDK's default OpenAI client at the FirstOps sidecarprompt/response inspection, PII detection, audit
Function toolsa FirstOps tool guardrail on each @function_toolblock a tool call + audit
MCP serverspoint the MCP server at the FirstOps proxyserver-side policy + credential brokering (the agent never holds the upstream token)

A note specific to OpenAI Agents: tool guardrails are block-only — they can deny a tool call but cannot rewrite its arguments. If you need to scrub arguments (not just block), wrap the function with the base-API @firstops.tool decorator. And guardrails attach per function tool, not on the Agent.

Setup#

1. Create an agent principal#

In the FirstOps dashboard, create an agent — you'll get an agent ID and a DPoP private-key PEM.

2. Install#

pip install "firstops[openai]"

3. Initialize FirstOps and route the LLM#

import firstops
from agents import set_default_openai_client, set_tracing_disabled
from openai import AsyncOpenAI

fo = firstops.init(
    agent_id="<agent-uuid>",
    private_key_pem=open("agent-key.pem").read(),
)

# The Agents SDK's model calls now flow through the FirstOps sidecar.
set_default_openai_client(AsyncOpenAI(
    base_url=firstops.llm_base_url("openai"),
    api_key="sk-...",          # passes through to OpenAI; FirstOps never stores it
))
set_tracing_disabled(True)     # so traces don't bypass governance

4. Govern function tools#

Attach the FirstOps guardrail to each @function_tool:

from agents import Agent, Runner, function_tool
from firstops.integrations.openai_agents import firstops_tool_input_guardrail

guard = firstops_tool_input_guardrail(fo)

@function_tool(tool_input_guardrails=[guard])
def send_email(to: str, body: str) -> str:
    ...

agent = Agent(name="assistant", tools=[send_email])
result = await Runner.run(agent, "email alice@example.com the Q3 summary")

5. (Optional) Add MCP servers through the FirstOps proxy#

from agents.mcp import MCPServerStreamableHttp

async with MCPServerStreamableHttp(
    name="notion",
    params={"url": firstops.mcp_url("<notion-connection-id>")},
) as notion:
    agent = Agent(name="assistant", mcp_servers=[notion], tools=[send_email])
    result = await Runner.run(agent, "Fetch the customer records from Notion.")

Full example — LLM + tool guardrail + MCP#

import asyncio
import firstops
from agents import (
    Agent, Runner, function_tool, set_default_openai_client, set_tracing_disabled,
)
from agents.mcp import MCPServerStreamableHttp
from firstops.integrations.openai_agents import firstops_tool_input_guardrail
from openai import AsyncOpenAI


async def main():
    fo = firstops.init(
        agent_id="<agent-uuid>",
        private_key_pem=open("agent-key.pem").read(),
    )
    try:
        set_default_openai_client(AsyncOpenAI(
            base_url=firstops.llm_base_url("openai"), api_key="sk-...",
        ))
        set_tracing_disabled(True)

        guard = firstops_tool_input_guardrail(fo)

        @function_tool(tool_input_guardrails=[guard])
        def write_to_file(filename: str, content: str) -> str:
            """Write text content to a local file."""
            with open(filename, "w") as f:
                f.write(content)
            return f"wrote {len(content)} bytes to {filename}"

        async with MCPServerStreamableHttp(
            name="notion", params={"url": firstops.mcp_url("<notion-connection-id>")},
        ) as notion:
            agent = Agent(name="assistant", mcp_servers=[notion], tools=[write_to_file])
            result = await Runner.run(
                agent,
                "Fetch the customer records from Notion and write them to customers.txt.",
            )
            print(result.final_output)
    finally:
        firstops.shutdown()


asyncio.run(main())

Multi-agent workflows#

For multi-agent handoffs, give each agent its own FirstOps principal by running it in its own process with its own init() and port:

# Researcher (process 1)
fo = firstops.init(agent_id="<research-uuid>", private_key_pem=research_key, port=9322)

# Writer (process 2)
fo = firstops.init(agent_id="<writer-uuid>", private_key_pem=writer_key, port=9323)

Each agent gets independent identity, permissions, and an independent audit trail.