Google ADK
Connect a Google Agent Development Kit (ADK) agent to Civic using streamable HTTP transport. Reference implementations are available in Python (Claude), TypeScript (Gemini), and Go (Gemini).
Architecture
User Message
|
v
Runner --> LlmAgent --> LLM (Claude / Gemini)
| |
| v
| MCPToolset --(StreamableHTTP)--> Civic MCP Hub
| | |
v v v
Session Tool Discovery Guardrails / Audit / Revocation
+ Invocation
| Component | Purpose |
|---|---|
| LlmAgent | Wraps an LLM with instructions and tools |
| MCPToolset | Connects to Civic's MCP Hub, discovers and invokes tools |
| Runner | Orchestrates agent execution and streams response events |
| SessionService | Manages conversation state |
Prerequisites
- A Civic account at app.civic.com with a configured toolkit
- A Civic token (how to get one)
How to generate a Civic token and configure toolkit URL parameters
Environment Variables
All implementations require:
CIVIC_URL=https://app.civic.com/hub/mcp?profile=your-toolkit
CIVIC_TOKEN=your-civic-token
Plus an LLM API key depending on the language — see each tab below.
Requirements: Python 3.10+, uv (recommended) or pip, an Anthropic API key
Installation
pip install google-adk python-dotenv
Additional env var
ANTHROPIC_API_KEY=your-anthropic-key
Connecting to Civic
ADK has native Anthropic model support via google.adk.models.anthropic_llm.Claude — no LiteLlm dependency needed. Register Claude with the model registry and reference the model by string:
import os
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from google.adk.models.anthropic_llm import Claude
from google.adk.models.registry import LLMRegistry
load_dotenv()
# Register Claude for native Anthropic model support
LLMRegistry.register(Claude)
root_agent = Agent(
model="claude-sonnet-4-6",
name="civic_assistant",
instruction="You are a helpful assistant with access to external tools through Civic.",
tools=[
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=os.environ["CIVIC_URL"],
headers={"Authorization": f"Bearer {os.environ['CIVIC_TOKEN']}"},
),
)
],
)
Running the Agent
Option A: ADK web console
ADK ships with a built-in development UI:
uv run adk web
Open http://localhost:8000 and select civic_agent from the dropdown.
Option B: Programmatic runner
import asyncio
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
async def main():
session_service = InMemorySessionService()
runner = Runner(
agent=root_agent,
app_name="my-app",
session_service=session_service,
)
await session_service.create_session(
app_name="my-app", user_id="user-1", session_id="session-1"
)
message = types.Content(
role="user",
parts=[types.Part(text="What events do I have today?")],
)
async for event in runner.run_async(
user_id="user-1", session_id="session-1", new_message=message
):
if event.is_final_response():
for part in event.content.parts:
if part.text:
print(part.text)
asyncio.run(main())
Use types.Part(text=...) — not types.Part.from_text(...). The from_text class method was removed in recent versions of the Google Gen AI SDK.
Requirements: Node.js v18+, a Google Gemini API key
Installation
npm install @google/adk dotenv
Additional env var
GOOGLE_GENAI_API_KEY=your-google-genai-api-key
Connecting to Civic
The TypeScript ADK uses MCPToolset with StreamableHTTPConnectionParams. Gemini is configured automatically via the GOOGLE_GENAI_API_KEY env var:
import 'dotenv/config';
import {
LlmAgent,
Runner,
InMemorySessionService,
MCPToolset,
isFinalResponse,
stringifyContent,
} from '@google/adk';
import type { Content } from '@google/genai';
const civicUrl = process.env.CIVIC_URL!;
const civicToken = process.env.CIVIC_TOKEN!;
const mcpToolset = new MCPToolset({
type: 'StreamableHTTPConnectionParams',
url: civicUrl,
transportOptions: {
requestInit: {
headers: { Authorization: `Bearer ${civicToken}` },
},
},
});
const agent = new LlmAgent({
model: 'gemini-2.5-flash',
name: 'civic_assistant',
instruction: 'You are a helpful assistant. Use the available tools when they can help.',
tools: [mcpToolset],
});
Running the Agent
async function main() {
const sessionService = new InMemorySessionService();
const runner = new Runner({
appName: 'civic-adk-ts',
agent,
sessionService,
});
const session = await sessionService.createSession({
appName: 'civic-adk-ts',
userId: 'user-1',
});
const message: Content = {
role: 'user',
parts: [{ text: 'What tools do you have?' }],
};
for await (const event of runner.runAsync({
userId: 'user-1',
sessionId: session.id,
newMessage: message,
})) {
if (isFinalResponse(event)) {
const text = stringifyContent(event);
if (text) console.log(text);
}
}
}
main();
Requirements: Go 1.23+, a Google API key
Additional env var
GOOGLE_API_KEY=your-google-api-key
Connecting to Civic
The Go ADK uses a custom http.RoundTripper to inject the Civic Bearer token into every outbound MCP request:
package main
import (
"context"
"net/http"
"os"
mcp "github.com/modelcontextprotocol/go-sdk/mcp"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/session"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/mcptoolset"
"google.golang.org/genai"
)
// bearerTransport injects Authorization into every request.
type bearerTransport struct {
token string
base http.RoundTripper
}
func (t *bearerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("Authorization", "Bearer "+t.token)
return t.base.RoundTrip(req)
}
Running the Agent
func main() {
ctx := context.Background()
civicURL := os.Getenv("CIVIC_URL")
civicToken := os.Getenv("CIVIC_TOKEN")
llm, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
APIKey: os.Getenv("GOOGLE_API_KEY"),
})
httpClient := &http.Client{
Transport: &bearerTransport{token: civicToken, base: http.DefaultTransport},
}
transport := &mcp.StreamableClientTransport{
Endpoint: civicURL,
HTTPClient: httpClient,
DisableStandaloneSSE: true,
}
mcpTools, _ := mcptoolset.New(mcptoolset.Config{Transport: transport})
root, _ := llmagent.New(llmagent.Config{
Name: "civic_assistant",
Instruction: "You are a helpful assistant. Use the available tools when they can help.",
Model: llm,
Toolsets: []tool.Toolset{mcpTools},
})
sessionSvc := session.InMemoryService()
r, _ := runner.New(runner.Config{
AppName: "civic-adk-go",
Agent: root,
SessionService: sessionSvc,
})
sess, _ := sessionSvc.Create(ctx, &session.CreateRequest{
AppName: "civic-adk-go",
UserID: "user-1",
})
userContent := &genai.Content{
Role: "user",
Parts: []*genai.Part{{Text: "What tools do you have?"}},
}
for event, err := range r.Run(ctx, "user-1", sess.Session.ID(), userContent, nil) {
if err != nil || event == nil || event.Content == nil {
continue
}
for _, part := range event.Content.Parts {
if part.Text != "" {
fmt.Print(part.Text)
}
}
}
}
Production Configuration
Lock to a specific toolkit using the profile URL parameter:
CIVIC_URL=https://app.civic.com/hub/mcp?profile=your-production-toolkit
Reference Implementation
Complete implementations in Python, TypeScript, and Go with deployment guides