axonflow-google-adk-plugin 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 getaxonflow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: axonflow-google-adk-plugin
3
+ Version: 1.0.0
4
+ Summary: AxonFlow governance plugin for Google Agent Development Kit (ADK)
5
+ Author-email: AxonFlow <hello@getaxonflow.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://getaxonflow.com
8
+ Project-URL: Documentation, https://docs.getaxonflow.com/docs/integration/google-adk
9
+ Project-URL: Repository, https://github.com/getaxonflow/axonflow-google-adk-plugin
10
+ Project-URL: Changelog, https://github.com/getaxonflow/axonflow-google-adk-plugin/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/getaxonflow/axonflow-google-adk-plugin/issues
12
+ Keywords: axonflow,google-adk,adk,ai-governance,llm,policy,compliance
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: google-adk>=2.0.0
27
+ Requires-Dist: axonflow>=8.2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # axonflow-google-adk-plugin
35
+
36
+ AxonFlow governance plugin for [Google Agent Development Kit (ADK)](https://adk.dev/).
37
+
38
+ Register `AxonFlowPlugin` once on a `Runner` and **every model call and every
39
+ tool call across every agent on that Runner** is governed by AxonFlow
40
+ policies: pre-check, HITL approval, deny short-circuit, audit trail, PII
41
+ redaction on tool I/O.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install axonflow-google-adk-plugin
47
+ ```
48
+
49
+ Requires `google-adk>=2.0` and `axonflow>=8.2.0` (AxonFlow Python SDK).
50
+
51
+ ## Quickstart (5 lines)
52
+
53
+ ```python
54
+ from google.adk.runners import InMemoryRunner
55
+ from google.adk.agents import LlmAgent
56
+ from axonflow_adk import AxonFlowPlugin
57
+
58
+ agent = LlmAgent(model="gemini-2.0-flash", name="loan_desk", instruction="...")
59
+ runner = InMemoryRunner(
60
+ agent=agent,
61
+ app_name="loan_desk",
62
+ plugins=[AxonFlowPlugin(
63
+ endpoint="http://localhost:8080",
64
+ client_id="loan-desk",
65
+ client_secret="secret-from-axonflow",
66
+ )],
67
+ )
68
+ ```
69
+
70
+ ## Hook → AxonFlow endpoint mapping
71
+
72
+ | ADK hook | AxonFlow call | Deny shape |
73
+ |------------------------------|---------------------------|------------------------------------------------|
74
+ | `before_model_callback` | `pre_check` | `LlmResponse` with policy-denial text |
75
+ | `after_model_callback` | `audit_llm_call` | never blocks (audit only) |
76
+ | `before_tool_callback` | `check_tool_input` | `{"error": "[AxonFlow] <reason>"}` |
77
+ | `after_tool_callback` | `check_tool_output` | redacted dict OR `{"error": ...}` on hard deny |
78
+ | `on_tool_error_callback` | `audit_tool_call` | never blocks (audit only) |
79
+ | `on_user_message_callback` | no-op (v1) | n/a |
80
+
81
+ The `on_user_message_callback` hook is intentionally a no-op in v1 — returning
82
+ non-None Content there would silently **replace** the user's message, which is
83
+ the wrong tool for governance.
84
+
85
+ ## HITL approval flow — 4-step
86
+
87
+ When AxonFlow policy evaluates to `require_approval`, the plugin runs the
88
+ full **4-step HITL flow** by default (`enable_hitl_polling=True`):
89
+
90
+ ```
91
+ before_model_callback / before_tool_callback
92
+
93
+ ├─ STEP 1 — gate (pre_check / check_tool_input)
94
+ │ returns blocked, BlockReason == "require_approval"
95
+
96
+ ├─ STEP 2 — POST /api/v1/hitl/queue
97
+ │ plugin calls client.create_hitl_request(request=HITLCreateInput(...))
98
+ │ returns approval_id (uuid)
99
+
100
+ ├─ STEP 3 — GET /api/v1/hitl/queue/{approval_id}
101
+ │ polled every approval_poll_interval_seconds (default 2s);
102
+ │ local consecutive-failure counter (NOT the shared
103
+ │ breaker) so a polling outage can't disable governance
104
+ │ for other in-flight calls
105
+
106
+ └─ STEP 4 — terminal state:
107
+ ├─ "approved" → return None (let LLM / tool proceed)
108
+ ├─ "rejected" | "expired" → return deny short-circuit
109
+ ├─ N consecutive poll failures → deny
110
+ └─ time > approval_max_wait_seconds → deny
111
+ ```
112
+
113
+ The plugin's `before_model_callback` and `before_tool_callback` both run
114
+ this flow. Detection is an exact-string match against the platform's
115
+ `require_approval` sentinel. Substring matching previously false-positived
116
+ on any policy whose reason text contained the word "approval".
117
+
118
+ The 4-step flow is the only **fail-closed** path in the plugin —
119
+ everything else fails open. Approvals are safety-critical; defaulting to
120
+ "allow" on an AxonFlow outage during an approval gate would defeat the
121
+ gate.
122
+
123
+ ### Approving / rejecting out-of-band
124
+
125
+ When step 2 returns an `approval_id`, the plugin emits a single INFO log:
126
+
127
+ ```
128
+ axonflow hitl AWAITING APPROVAL: request_id=<uuid>; approve via
129
+ POST /api/v1/hitl/queue/<uuid>/{approve|reject}
130
+ ```
131
+
132
+ The reviewer (UI, Slack bot, internal portal) posts the decision via:
133
+
134
+ ```bash
135
+ # Approve
136
+ curl -X POST $AXONFLOW_ENDPOINT/api/v1/hitl/queue/<approval_id>/approve \
137
+ -H 'Content-Type: application/json' \
138
+ -d '{"reviewer_id":"compliance","reviewer_email":"compliance@bank.example"}'
139
+
140
+ # Reject (same shape)
141
+ curl -X POST $AXONFLOW_ENDPOINT/api/v1/hitl/queue/<approval_id>/reject \
142
+ -H 'Content-Type: application/json' \
143
+ -d '{"reviewer_id":"compliance","reviewer_email":"compliance@bank.example"}'
144
+ ```
145
+
146
+ ### Opting out — deny-fast mode
147
+
148
+ Set `enable_hitl_polling=False` on the config to short-circuit
149
+ `require_approval` immediately without enqueuing a row. The host app
150
+ then drives its own approval workflow.
151
+
152
+ ## Authenticating in enterprise mode
153
+
154
+ ADK does not carry a first-class `user_token` concept. To propagate the
155
+ end-user identity AxonFlow's enterprise-mode policy enforcement requires,
156
+ set `state["axonflow_user_token"]` to a valid JWT on the session BEFORE
157
+ calling `runner.run_async(...)`:
158
+
159
+ ```python
160
+ session = runner.session_service.create_session(
161
+ app_name="loan_desk", user_id="cust-001", session_id="sess-A",
162
+ )
163
+ session.state["axonflow_user_token"] = generate_axonflow_jwt(user_id="cust-001")
164
+ ```
165
+
166
+ For **community mode** (no tenant signing key), leave the state key
167
+ unset; the plugin will use `config.default_user_token` (default
168
+ `"anonymous"`).
169
+
170
+ ## Failure semantics
171
+
172
+ A buggy or unreachable AxonFlow **must not** break the agent. The plugin
173
+ ships with:
174
+
175
+ - **Per-hook timeout** (default 5s, configurable via `call_timeout_seconds`)
176
+ - **Half-open circuit breaker** (default open after 5 consecutive failures,
177
+ recover after 30s). HALF_OPEN admits exactly one probe; concurrent
178
+ hooks during recovery are skipped without leaking a thundering herd.
179
+ - **Fail-open default** — every hook except `_await_hitl_decision`
180
+ returns `None` on error/timeout/open-circuit, letting the model or
181
+ tool call proceed.
182
+
183
+ ## MCP toolset helper
184
+
185
+ ```python
186
+ from google.adk.agents import LlmAgent
187
+ from axonflow_adk import axonflow_mcp_toolset
188
+
189
+ agent = LlmAgent(
190
+ model="gemini-2.0-flash",
191
+ name="postgres_governed",
192
+ instruction="Answer questions about the production DB.",
193
+ tools=[axonflow_mcp_toolset(
194
+ endpoint="http://localhost:8080",
195
+ client_id="my-app",
196
+ client_secret="secret",
197
+ )],
198
+ )
199
+ ```
200
+
201
+ ## Run the example
202
+
203
+ ```bash
204
+ pip install axonflow-google-adk-plugin
205
+ export GOOGLE_API_KEY=...
206
+ export AXONFLOW_ENDPOINT=http://localhost:8080
207
+ export AXONFLOW_CLIENT_ID=loan-desk
208
+ export AXONFLOW_CLIENT_SECRET=...
209
+
210
+ python -m examples.loan_disbursement_agent
211
+ # or: python examples/loan_disbursement_agent.py
212
+ ```
213
+
214
+ ## Tests
215
+
216
+ ```bash
217
+ pip install -e ".[dev]"
218
+ pytest tests/ -v
219
+ ```
220
+
221
+ ## Documentation
222
+
223
+ Full integration guide: [docs.getaxonflow.com/docs/integration/google-adk](https://docs.getaxonflow.com/docs/integration/google-adk/)
224
+
225
+ ## License
226
+
227
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,194 @@
1
+ # axonflow-google-adk-plugin
2
+
3
+ AxonFlow governance plugin for [Google Agent Development Kit (ADK)](https://adk.dev/).
4
+
5
+ Register `AxonFlowPlugin` once on a `Runner` and **every model call and every
6
+ tool call across every agent on that Runner** is governed by AxonFlow
7
+ policies: pre-check, HITL approval, deny short-circuit, audit trail, PII
8
+ redaction on tool I/O.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install axonflow-google-adk-plugin
14
+ ```
15
+
16
+ Requires `google-adk>=2.0` and `axonflow>=8.2.0` (AxonFlow Python SDK).
17
+
18
+ ## Quickstart (5 lines)
19
+
20
+ ```python
21
+ from google.adk.runners import InMemoryRunner
22
+ from google.adk.agents import LlmAgent
23
+ from axonflow_adk import AxonFlowPlugin
24
+
25
+ agent = LlmAgent(model="gemini-2.0-flash", name="loan_desk", instruction="...")
26
+ runner = InMemoryRunner(
27
+ agent=agent,
28
+ app_name="loan_desk",
29
+ plugins=[AxonFlowPlugin(
30
+ endpoint="http://localhost:8080",
31
+ client_id="loan-desk",
32
+ client_secret="secret-from-axonflow",
33
+ )],
34
+ )
35
+ ```
36
+
37
+ ## Hook → AxonFlow endpoint mapping
38
+
39
+ | ADK hook | AxonFlow call | Deny shape |
40
+ |------------------------------|---------------------------|------------------------------------------------|
41
+ | `before_model_callback` | `pre_check` | `LlmResponse` with policy-denial text |
42
+ | `after_model_callback` | `audit_llm_call` | never blocks (audit only) |
43
+ | `before_tool_callback` | `check_tool_input` | `{"error": "[AxonFlow] <reason>"}` |
44
+ | `after_tool_callback` | `check_tool_output` | redacted dict OR `{"error": ...}` on hard deny |
45
+ | `on_tool_error_callback` | `audit_tool_call` | never blocks (audit only) |
46
+ | `on_user_message_callback` | no-op (v1) | n/a |
47
+
48
+ The `on_user_message_callback` hook is intentionally a no-op in v1 — returning
49
+ non-None Content there would silently **replace** the user's message, which is
50
+ the wrong tool for governance.
51
+
52
+ ## HITL approval flow — 4-step
53
+
54
+ When AxonFlow policy evaluates to `require_approval`, the plugin runs the
55
+ full **4-step HITL flow** by default (`enable_hitl_polling=True`):
56
+
57
+ ```
58
+ before_model_callback / before_tool_callback
59
+
60
+ ├─ STEP 1 — gate (pre_check / check_tool_input)
61
+ │ returns blocked, BlockReason == "require_approval"
62
+
63
+ ├─ STEP 2 — POST /api/v1/hitl/queue
64
+ │ plugin calls client.create_hitl_request(request=HITLCreateInput(...))
65
+ │ returns approval_id (uuid)
66
+
67
+ ├─ STEP 3 — GET /api/v1/hitl/queue/{approval_id}
68
+ │ polled every approval_poll_interval_seconds (default 2s);
69
+ │ local consecutive-failure counter (NOT the shared
70
+ │ breaker) so a polling outage can't disable governance
71
+ │ for other in-flight calls
72
+
73
+ └─ STEP 4 — terminal state:
74
+ ├─ "approved" → return None (let LLM / tool proceed)
75
+ ├─ "rejected" | "expired" → return deny short-circuit
76
+ ├─ N consecutive poll failures → deny
77
+ └─ time > approval_max_wait_seconds → deny
78
+ ```
79
+
80
+ The plugin's `before_model_callback` and `before_tool_callback` both run
81
+ this flow. Detection is an exact-string match against the platform's
82
+ `require_approval` sentinel. Substring matching previously false-positived
83
+ on any policy whose reason text contained the word "approval".
84
+
85
+ The 4-step flow is the only **fail-closed** path in the plugin —
86
+ everything else fails open. Approvals are safety-critical; defaulting to
87
+ "allow" on an AxonFlow outage during an approval gate would defeat the
88
+ gate.
89
+
90
+ ### Approving / rejecting out-of-band
91
+
92
+ When step 2 returns an `approval_id`, the plugin emits a single INFO log:
93
+
94
+ ```
95
+ axonflow hitl AWAITING APPROVAL: request_id=<uuid>; approve via
96
+ POST /api/v1/hitl/queue/<uuid>/{approve|reject}
97
+ ```
98
+
99
+ The reviewer (UI, Slack bot, internal portal) posts the decision via:
100
+
101
+ ```bash
102
+ # Approve
103
+ curl -X POST $AXONFLOW_ENDPOINT/api/v1/hitl/queue/<approval_id>/approve \
104
+ -H 'Content-Type: application/json' \
105
+ -d '{"reviewer_id":"compliance","reviewer_email":"compliance@bank.example"}'
106
+
107
+ # Reject (same shape)
108
+ curl -X POST $AXONFLOW_ENDPOINT/api/v1/hitl/queue/<approval_id>/reject \
109
+ -H 'Content-Type: application/json' \
110
+ -d '{"reviewer_id":"compliance","reviewer_email":"compliance@bank.example"}'
111
+ ```
112
+
113
+ ### Opting out — deny-fast mode
114
+
115
+ Set `enable_hitl_polling=False` on the config to short-circuit
116
+ `require_approval` immediately without enqueuing a row. The host app
117
+ then drives its own approval workflow.
118
+
119
+ ## Authenticating in enterprise mode
120
+
121
+ ADK does not carry a first-class `user_token` concept. To propagate the
122
+ end-user identity AxonFlow's enterprise-mode policy enforcement requires,
123
+ set `state["axonflow_user_token"]` to a valid JWT on the session BEFORE
124
+ calling `runner.run_async(...)`:
125
+
126
+ ```python
127
+ session = runner.session_service.create_session(
128
+ app_name="loan_desk", user_id="cust-001", session_id="sess-A",
129
+ )
130
+ session.state["axonflow_user_token"] = generate_axonflow_jwt(user_id="cust-001")
131
+ ```
132
+
133
+ For **community mode** (no tenant signing key), leave the state key
134
+ unset; the plugin will use `config.default_user_token` (default
135
+ `"anonymous"`).
136
+
137
+ ## Failure semantics
138
+
139
+ A buggy or unreachable AxonFlow **must not** break the agent. The plugin
140
+ ships with:
141
+
142
+ - **Per-hook timeout** (default 5s, configurable via `call_timeout_seconds`)
143
+ - **Half-open circuit breaker** (default open after 5 consecutive failures,
144
+ recover after 30s). HALF_OPEN admits exactly one probe; concurrent
145
+ hooks during recovery are skipped without leaking a thundering herd.
146
+ - **Fail-open default** — every hook except `_await_hitl_decision`
147
+ returns `None` on error/timeout/open-circuit, letting the model or
148
+ tool call proceed.
149
+
150
+ ## MCP toolset helper
151
+
152
+ ```python
153
+ from google.adk.agents import LlmAgent
154
+ from axonflow_adk import axonflow_mcp_toolset
155
+
156
+ agent = LlmAgent(
157
+ model="gemini-2.0-flash",
158
+ name="postgres_governed",
159
+ instruction="Answer questions about the production DB.",
160
+ tools=[axonflow_mcp_toolset(
161
+ endpoint="http://localhost:8080",
162
+ client_id="my-app",
163
+ client_secret="secret",
164
+ )],
165
+ )
166
+ ```
167
+
168
+ ## Run the example
169
+
170
+ ```bash
171
+ pip install axonflow-google-adk-plugin
172
+ export GOOGLE_API_KEY=...
173
+ export AXONFLOW_ENDPOINT=http://localhost:8080
174
+ export AXONFLOW_CLIENT_ID=loan-desk
175
+ export AXONFLOW_CLIENT_SECRET=...
176
+
177
+ python -m examples.loan_disbursement_agent
178
+ # or: python examples/loan_disbursement_agent.py
179
+ ```
180
+
181
+ ## Tests
182
+
183
+ ```bash
184
+ pip install -e ".[dev]"
185
+ pytest tests/ -v
186
+ ```
187
+
188
+ ## Documentation
189
+
190
+ Full integration guide: [docs.getaxonflow.com/docs/integration/google-adk](https://docs.getaxonflow.com/docs/integration/google-adk/)
191
+
192
+ ## License
193
+
194
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,34 @@
1
+ # Copyright 2026 AxonFlow
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """AxonFlow governance plugin for Google Agent Development Kit (ADK).
5
+
6
+ Register `AxonFlowPlugin` on a `Runner` and every model + tool call across
7
+ every agent on that runner is governed by AxonFlow policies, with HITL
8
+ approval, denial short-circuits, and an audit trail.
9
+
10
+ from google.adk.runners import InMemoryRunner
11
+ from axonflow_adk import AxonFlowPlugin
12
+
13
+ runner = InMemoryRunner(
14
+ agent=root_agent,
15
+ app_name="loan_desk",
16
+ plugins=[AxonFlowPlugin(
17
+ endpoint="http://localhost:8080",
18
+ client_id="loan-desk",
19
+ client_secret="...",
20
+ )],
21
+ )
22
+ """
23
+
24
+ from axonflow_adk._version import __version__
25
+ from axonflow_adk.plugin import AxonFlowPlugin, ApprovalTimeout, ApprovalRejected
26
+ from axonflow_adk.mcp_helper import axonflow_mcp_toolset
27
+
28
+ __all__ = [
29
+ "__version__",
30
+ "ApprovalRejected",
31
+ "ApprovalTimeout",
32
+ "AxonFlowPlugin",
33
+ "axonflow_mcp_toolset",
34
+ ]
@@ -0,0 +1,4 @@
1
+ # Copyright 2026 AxonFlow
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ __version__ = "1.0.0"
@@ -0,0 +1,90 @@
1
+ # Copyright 2026 AxonFlow
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """ADK `MCPToolset` helper for the AxonFlow agent's MCP server.
5
+
6
+ The AxonFlow agent ships an MCP endpoint at `/mcp/` on the same host as
7
+ its REST API. This module returns an `McpToolset` configured against that
8
+ endpoint over Streamable HTTP, so ADK callers can register AxonFlow's
9
+ governed MCP tools (e.g. PostgreSQL, Snowflake, GCS) with one line:
10
+
11
+ from axonflow_adk import axonflow_mcp_toolset
12
+
13
+ toolset = axonflow_mcp_toolset(
14
+ endpoint="http://localhost:8080",
15
+ client_id="my-app",
16
+ client_secret="...",
17
+ )
18
+
19
+ Reference: https://adk.dev/tools-custom/mcp-tools/
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import base64
25
+ from typing import Any
26
+
27
+
28
+ def axonflow_mcp_toolset(
29
+ endpoint: str,
30
+ *,
31
+ client_id: str | None = None,
32
+ client_secret: str | None = None,
33
+ bearer_token: str | None = None,
34
+ mcp_path: str = "/mcp/",
35
+ extra_headers: dict[str, str] | None = None,
36
+ ) -> Any:
37
+ """Return an ADK `McpToolset` pointed at AxonFlow's MCP server.
38
+
39
+ Authentication shape (R3 HIGH-3 — the platform's MCP server expects
40
+ one of):
41
+
42
+ * `Authorization: Basic <base64(client_id:client_secret)>` when
43
+ `client_id` AND `client_secret` are provided.
44
+ * `Authorization: Bearer <token>` when `bearer_token` is provided.
45
+ * Anonymous (no header) when none are provided — community-mode.
46
+
47
+ Custom `X-AxonFlow-*` headers (a prior shape) are NOT recognized by
48
+ the platform and would result in silently-anonymous calls bypassing
49
+ per-client / per-tenant policy scoping. Use the canonical
50
+ Authorization header instead.
51
+
52
+ Args:
53
+ endpoint: AxonFlow agent base URL (e.g. `http://localhost:8080`).
54
+ Trailing slash is stripped; `mcp_path` is appended.
55
+ client_id: AxonFlow client identifier (community/enterprise).
56
+ client_secret: AxonFlow client secret (community/enterprise).
57
+ bearer_token: Pre-issued bearer token (overrides client_id/secret
58
+ when set).
59
+ mcp_path: Path component of the AxonFlow MCP endpoint. Defaults
60
+ to `/mcp/` which is the agent's canonical path.
61
+ extra_headers: Optional additional headers (tenant scoping,
62
+ tracing context) merged into the connection params.
63
+
64
+ Returns:
65
+ `McpToolset` ready to drop into `LlmAgent(tools=[...])`.
66
+
67
+ Raises:
68
+ ImportError: if `google-adk` is not installed.
69
+ """
70
+ from google.adk.tools.mcp_tool import McpToolset
71
+ from google.adk.tools.mcp_tool.mcp_session_manager import (
72
+ StreamableHTTPConnectionParams,
73
+ )
74
+
75
+ headers: dict[str, str] = {}
76
+ if bearer_token:
77
+ headers["Authorization"] = f"Bearer {bearer_token}"
78
+ elif client_id and client_secret:
79
+ raw = f"{client_id}:{client_secret}".encode()
80
+ headers["Authorization"] = "Basic " + base64.b64encode(raw).decode("ascii")
81
+ if extra_headers:
82
+ headers.update(extra_headers)
83
+
84
+ url = endpoint.rstrip("/") + mcp_path
85
+ return McpToolset(
86
+ connection_params=StreamableHTTPConnectionParams(
87
+ url=url,
88
+ headers=headers,
89
+ )
90
+ )