Govern MCP, tool calls, and LLM traffic for AI agents — across LangGraph, Claude Agent SDK, OpenAI Agents, and Google ADK.
Project description
FirstOps Python SDK
Govern what your AI agents do. FirstOps applies identity, policy enforcement, credential brokering, and audit to every LLM call, tool call, and MCP call your agent makes — across LangGraph, the Claude Agent SDK, the OpenAI Agents SDK, and Google ADK, or any custom loop.
Install
pip install "firstops[langgraph]" # LangGraph + LangChain
pip install "firstops[claude]" # Claude Agent SDK
pip install "firstops[openai]" # OpenAI Agents SDK
pip install "firstops[adk]" # Google ADK
pip install "firstops[all]" # all of the above
pip install firstops # core only (management client / custom loops)
Python 3.10+. Core deps are just cryptography and httpx — your agent framework comes in via the extra you pick.
What FirstOps governs
| Surface | How it's wired | What you get |
|---|---|---|
| LLM calls | point the model base_url at the local sidecar |
inspect prompts/responses, scrub PII, block, audit |
| Tool calls | one adapter (or @firstops.tool) |
block / rewrite args / audit — including framework built-ins |
| MCP servers | point the MCP client at the local proxy | server-side policy + credential brokering (the agent never holds the upstream token) |
Every action is evaluated by FirstOps and returns allow / deny / modify — your agent logic doesn't change.
Quick start (LangGraph)
import firstops
from firstops.integrations.langgraph import FirstOpsMiddleware
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
fo = firstops.init(
agent_id="<agent-uuid>", # from the FirstOps dashboard
private_key_pem=open("agent-key.pem").read(),
)
# Route the LLM through FirstOps; wire one middleware to govern every tool call.
llm = ChatOpenAI(model="gpt-4o-mini", base_url=firstops.llm_base_url("openai"), api_key="sk-...")
agent = create_agent(model=llm, tools=[...], middleware=[FirstOpsMiddleware(fo)])
agent.invoke({"messages": [{"role": "user", "content": "..."}]})
The whole integration is init() + a base_url swap + one middleware. See examples/ for runnable agents, including MCP.
Other harnesses
Claude Agent SDK — one PreToolUse hook governs every tool (built-ins, MCP, custom):
from claude_agent_sdk import query, ClaudeAgentOptions
from firstops.integrations.claude import firstops_hooks
options = ClaudeAgentOptions(hooks=firstops_hooks(fo), permission_mode="bypassPermissions")
async for _ in query(prompt="...", options=options):
pass
OpenAI Agents SDK — a guardrail per tool + the model routed through the sidecar:
from agents import Agent, function_tool, set_default_openai_client
from firstops.integrations.openai_agents import firstops_tool_input_guardrail
from openai import AsyncOpenAI
set_default_openai_client(AsyncOpenAI(base_url=firstops.llm_base_url("openai"), api_key="sk-..."))
guard = firstops_tool_input_guardrail(fo)
@function_tool(tool_input_guardrails=[guard])
def send_email(to: str, body: str) -> str: ...
Google ADK — one before_tool_callback governs every tool (block + rewrite args):
from google.adk.agents import LlmAgent
from firstops.integrations.google_adk import firstops_before_tool_callback
agent = LlmAgent(name="assistant", model=..., tools=[...],
before_tool_callback=firstops_before_tool_callback(fo))
Any framework / custom loop — the base API:
@firstops.tool # govern any callable: block / scrub args / audit
def send_email(to: str, body: str): ...
MCP servers
Point your MCP client at the local proxy; FirstOps brokers the upstream credentials.
from langchain_mcp_adapters.client import MultiServerMCPClient
mcp = MultiServerMCPClient({"notion": {"url": firstops.mcp_url("<connection-id>"), "transport": "streamable_http"}})
tools = await mcp.get_tools()
Examples
Runnable agents in examples/ — each governs the LLM and tool calls; the *_mcp variants add a Notion MCP server:
- LangGraph —
langgraph_basic.py,langgraph_notion_mcp.py - Claude Agent SDK —
claude_sdk_basic.py,claude_sdk_mcp.py - OpenAI Agents SDK —
openai_agents_basic.py,openai_agents_mcp.py - Google ADK —
google_adk_basic.py
See examples/README.md for the env vars to run them.
Management client
Provision agents and connections from your backend:
from firstops import FirstOps
admin = FirstOps(api_key="fo_key_...")
agent = admin.agents.create(name="research-bot") # -> id + private_key (shown once)
admin.connections.register(principal_id=agent.id, name="slack", upstream_url="https://mcp.slack.com/sse")
How it works
firstops.init() starts a local sidecar and establishes the agent's identity (a DPoP-bound principal — RFC 9449). Tool and LLM actions are forwarded to the FirstOps gateway, which evaluates them against your policies and returns the verdict; MCP and LLM traffic flow through the sidecar with credentials brokered. Enforcement fails open on infrastructure errors; authentication fails closed.
Documentation
- Docs home
- Guides: LangChain / LangGraph · Claude Agent SDK · OpenAI Agents SDK · Google ADK
- Concepts: Identity · Enforcement · Connections
- Repository
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file firstops-0.3.0.tar.gz.
File metadata
- Download URL: firstops-0.3.0.tar.gz
- Upload date:
- Size: 71.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b5271845ac7db4baf8d7c415947219c7dd07fcb18744220d6cd5cb073968339
|
|
| MD5 |
875cc31c00177d1e0f0b022582473ec9
|
|
| BLAKE2b-256 |
6ede4005bbefa3b4076d8de8cbdabbfacfb13ab07d393e83bc97e74c68c74b85
|
Provenance
The following attestation bundles were made for firstops-0.3.0.tar.gz:
Publisher:
publish.yml on firstops-dev/firstops-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
firstops-0.3.0.tar.gz -
Subject digest:
6b5271845ac7db4baf8d7c415947219c7dd07fcb18744220d6cd5cb073968339 - Sigstore transparency entry: 1821265268
- Sigstore integration time:
-
Permalink:
firstops-dev/firstops-python@4f25f8d6e1abff08c80ac51f5010edc73ede960b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/firstops-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f25f8d6e1abff08c80ac51f5010edc73ede960b -
Trigger Event:
push
-
Statement type:
File details
Details for the file firstops-0.3.0-py3-none-any.whl.
File metadata
- Download URL: firstops-0.3.0-py3-none-any.whl
- Upload date:
- Size: 38.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5967e6f3b8cbc5e2c40f4c6069ba3d2fefe22f0b68d35d7ab7c6d0716a50d74c
|
|
| MD5 |
e5a66e13ea39ddb6fa9063262235250c
|
|
| BLAKE2b-256 |
43a13586f9390a328c6e4243ed79d7d404c92e66a5286d075c43ffa23bcdab16
|
Provenance
The following attestation bundles were made for firstops-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on firstops-dev/firstops-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
firstops-0.3.0-py3-none-any.whl -
Subject digest:
5967e6f3b8cbc5e2c40f4c6069ba3d2fefe22f0b68d35d7ab7c6d0716a50d74c - Sigstore transparency entry: 1821265310
- Sigstore integration time:
-
Permalink:
firstops-dev/firstops-python@4f25f8d6e1abff08c80ac51f5010edc73ede960b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/firstops-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f25f8d6e1abff08c80ac51f5010edc73ede960b -
Trigger Event:
push
-
Statement type: