LangChain / LangGraph Integration#
Govern a LangChain / LangGraph agent with FirstOps. With one middleware and a base-URL swap, FirstOps applies identity, policy enforcement, credential brokering, and audit to every action the agent takes.
What FirstOps governs#
| Surface | How it's wired | What you get |
|---|---|---|
| LLM calls | point the model's base_url at the FirstOps sidecar | prompt/response inspection, PII scrubbing, prompt-injection detection, audit |
| Tool calls | one FirstOpsMiddleware on the agent | block / rewrite args / audit on every tool, including framework built-ins |
| MCP servers | point the MCP client at the FirstOps proxy | server-side policy + credential brokering (the agent never sees the upstream token) |
Each action is evaluated by FirstOps and returns allow / deny / modify — your agent code doesn't change.
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 (shown once — store it securely).
2. Install#
pip install "firstops[langgraph]"
The langgraph extra pulls in langchain, langgraph, langchain-openai, and langchain-mcp-adapters.
3. Initialize FirstOps#
init() establishes the agent's identity and starts a local sidecar. Do this once at process start.
import firstops
fo = firstops.init(
agent_id="<agent-uuid>",
private_key_pem=open("agent-key.pem").read(),
)
4. Route the LLM through FirstOps#
Swap the model's base_url to the FirstOps sidecar. Your OpenAI key passes through to OpenAI — FirstOps never stores it.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4o-mini",
base_url=firstops.llm_base_url("openai"),
api_key="sk-...", # or set OPENAI_API_KEY
)
5. Govern tool calls with one middleware#
from langchain.agents import create_agent
from firstops.integrations.langgraph import FirstOpsMiddleware
agent = create_agent(
model=llm,
tools=[your_tool_1, your_tool_2],
middleware=[FirstOpsMiddleware(fo)], # ← governs every tool call
)
6. (Optional) Add MCP servers#
Point the MCP client at the FirstOps proxy. FirstOps brokers the upstream credentials — your agent never holds the Notion/GitHub/Slack token.
from langchain_mcp_adapters.client import MultiServerMCPClient
mcp = MultiServerMCPClient({
"notion": {
"url": firstops.mcp_url("<notion-connection-id>"),
"transport": "streamable_http",
},
})
mcp_tools = await mcp.get_tools()
Full example — MCP + local tool, all governed#
This agent fetches customer records from Notion (MCP) and writes them to a local file (a plain Python tool). Every LLM call, MCP call, and local tool call is governed by FirstOps.
import asyncio
import firstops
from firstops.integrations.langgraph import FirstOpsMiddleware
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
@tool
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 def main():
fo = firstops.init(
agent_id="<agent-uuid>",
private_key_pem=open("agent-key.pem").read(),
)
try:
# MCP tools through the FirstOps proxy (credentials brokered server-side)
mcp = MultiServerMCPClient({
"notion": {
"url": firstops.mcp_url("<notion-connection-id>"),
"transport": "streamable_http",
},
})
mcp_tools = await mcp.get_tools()
# LLM through the FirstOps chain-link
llm = ChatOpenAI(
model="gpt-4o-mini",
base_url=firstops.llm_base_url("openai"),
api_key="sk-...",
)
agent = create_agent(
model=llm,
tools=mcp_tools + [write_to_file],
middleware=[FirstOpsMiddleware(fo)],
)
result = await agent.ainvoke({"messages": [{
"role": "user",
"content": "Fetch the customer records from Notion and write them to customers.txt.",
}]})
print(result["messages"][-1].content)
finally:
firstops.shutdown()
asyncio.run(main())
Every tool call, MCP call, and model call in this agent is evaluated against your policies — block a tool, scrub PII from arguments or prompts, or just audit — without changing the agent logic.
Multi-agent (LangGraph)#
Give each agent in a LangGraph workflow its own FirstOps principal by running it in its own process with its own init():
# Research agent (process 1)
fo = firstops.init(agent_id="<research-uuid>", private_key_pem=research_key, port=9322)
# Action agent (process 2)
fo = firstops.init(agent_id="<action-uuid>", private_key_pem=action_key, port=9323)
Each agent gets independent identity, permissions, and an independent audit trail — even inside the same workflow.