Google ADK Integration#

Integrate FirstOps with the Google Agent Development Kit (ADK) to govern your Google ADK agents.

Overview#

Google ADK agents connect to external tools via MCP. FirstOps sits between the agent and those tools, providing identity, policy enforcement, credential brokering, and audit.

Setup#

1. Create an agent principal#

Create an autonomous agent principal in the FirstOps dashboard. You receive an agent ID and a DPoP private key PEM file.

2. Register connections#

Register the MCP connections your agent needs:

fo connection add --name notion --url https://mcp.notion.com/mcp --auth bearer --token ntn_secret_... --agent <agent-name>

Note the connection ID returned — you need it in your agent code.

3. Install dependencies#

pip install google-adk firstops

4. Configure and run the agent#

import asyncio
import os
import firstops
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from google.genai import types as genai_types

PROXY_PORT = 9322
NOTION_CONN_ID = "<notion-conn-id>"
GATEWAY_URL = os.environ.get("FIRSTOPS_GATEWAY_URL", "https://api.firstops.dev")

# Start the FirstOps proxy — handles DPoP authentication per request
firstops.init(
    agent_id="<uuid>",
    private_key_pem=open("key.pem").read(),
    port=PROXY_PORT,
    gateway_url=GATEWAY_URL,
)


async def run():
    # Connect to Notion MCP server through the FirstOps governance proxy
    notion_toolset = McpToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=f"http://127.0.0.1:{PROXY_PORT}/mcp/proxy/{NOTION_CONN_ID}",
        ),
    )

    # Create an ADK agent with the governed MCP tools
    agent = LlmAgent(
        model="gemini-2.0-flash",
        name="my_adk_agent",
        instruction="You are a helpful assistant with access to Notion.",
        tools=[notion_toolset],
    )

    session_service = InMemorySessionService()
    session = await session_service.create_session(
        app_name="my-app", user_id="user-1",
    )
    runner = Runner(
        app_name="my-app", agent=agent, session_service=session_service,
    )

    message = genai_types.Content(
        role="user",
        parts=[genai_types.Part(text="Summarize the latest project updates from Notion")],
    )

    async for event in runner.run_async(
        user_id="user-1", session_id=session.id, new_message=message,
    ):
        if event.content and event.content.parts:
            for part in event.content.parts:
                if hasattr(part, "text") and part.text:
                    print(part.text)

    await notion_toolset.close()


asyncio.run(run())

# Clean up on shutdown
firstops.shutdown()

Every tool call flows through FirstOps:

  • A fresh DPoP proof is generated per request
  • Policies are evaluated against the request content
  • Upstream credentials are injected by the proxy
  • The full interaction is logged in the audit trail

What gets enforced#

All policies attached to the agent's access group apply:

  • Deny rules: Dangerous operations are blocked before reaching the upstream tool
  • Modify rules: PII is redacted before reaching upstream tools
  • Flag rules: Suspicious patterns are logged for review
  • Outbound set: Requests to tools outside the permitted set are blocked