axonflow 1.0.0__tar.gz → 1.4.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.
Files changed (45) hide show
  1. {axonflow-1.0.0 → axonflow-1.4.0}/PKG-INFO +126 -5
  2. {axonflow-1.0.0 → axonflow-1.4.0}/README.md +125 -4
  3. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/__init__.py +1 -1
  4. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/client.py +182 -27
  5. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/code_governance.py +1 -0
  6. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/policies.py +15 -2
  7. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/types.py +101 -4
  8. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/PKG-INFO +126 -5
  9. {axonflow-1.0.0 → axonflow-1.4.0}/pyproject.toml +1 -1
  10. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_auth_headers.py +54 -95
  11. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_client.py +174 -7
  12. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_selfhosted_zero_config.py +3 -13
  13. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_types.py +4 -4
  14. {axonflow-1.0.0 → axonflow-1.4.0}/LICENSE +0 -0
  15. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/exceptions.py +0 -0
  16. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/__init__.py +0 -0
  17. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/anthropic.py +0 -0
  18. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/base.py +0 -0
  19. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/bedrock.py +0 -0
  20. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/gemini.py +0 -0
  21. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/ollama.py +0 -0
  22. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/openai.py +0 -0
  23. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/py.typed +0 -0
  24. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/__init__.py +0 -0
  25. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/cache.py +0 -0
  26. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/logging.py +0 -0
  27. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/retry.py +0 -0
  28. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/SOURCES.txt +0 -0
  29. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/dependency_links.txt +0 -0
  30. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/requires.txt +0 -0
  31. {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/top_level.txt +0 -0
  32. {axonflow-1.0.0 → axonflow-1.4.0}/setup.cfg +0 -0
  33. {axonflow-1.0.0 → axonflow-1.4.0}/setup.py +0 -0
  34. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_audit.py +0 -0
  35. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_client_coverage.py +0 -0
  36. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_contract.py +0 -0
  37. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_cost_controls.py +0 -0
  38. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_exceptions.py +0 -0
  39. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_gateway.py +0 -0
  40. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_integration.py +0 -0
  41. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors.py +0 -0
  42. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors_coverage.py +0 -0
  43. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors_execution.py +0 -0
  44. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_policies.py +0 -0
  45. {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axonflow
3
- Version: 1.0.0
3
+ Version: 1.4.0
4
4
  Summary: AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code
5
5
  Author-email: AxonFlow <dev@getaxonflow.com>
6
6
  Maintainer-email: AxonFlow <dev@getaxonflow.com>
@@ -202,6 +202,38 @@ result = await client.query_connector(
202
202
  )
203
203
  ```
204
204
 
205
+ ### MCP Policy Features (v3.2.0)
206
+
207
+ **Exfiltration Detection** - Prevent large-scale data extraction:
208
+
209
+ ```python
210
+ # Query with exfiltration limits (default: 10K rows, 10MB)
211
+ result = await client.query_connector(
212
+ user_token="user-jwt",
213
+ connector_name="postgres",
214
+ operation="query",
215
+ params={"sql": "SELECT * FROM customers"}
216
+ )
217
+
218
+ # Check exfiltration info
219
+ if result.policy_info.exfiltration_check.exceeded:
220
+ print(f"Limit exceeded: {result.policy_info.exfiltration_check.limit_type}")
221
+
222
+ # Configure: MCP_MAX_ROWS_PER_QUERY=1000, MCP_MAX_BYTES_PER_QUERY=5242880
223
+ ```
224
+
225
+ **Dynamic Policy Evaluation** - Orchestrator-based rate limiting, budget controls:
226
+
227
+ ```python
228
+ # Response includes dynamic policy info when enabled
229
+ if result.policy_info.dynamic_policy_info.orchestrator_reachable:
230
+ print(f"Policies evaluated: {result.policy_info.dynamic_policy_info.policies_evaluated}")
231
+ for policy in result.policy_info.dynamic_policy_info.matched_policies:
232
+ print(f" {policy.policy_name}: {policy.action}")
233
+
234
+ # Enable: MCP_DYNAMIC_POLICIES_ENABLED=true
235
+ ```
236
+
205
237
  ### Multi-Agent Planning
206
238
 
207
239
  Generate and execute multi-agent plans:
@@ -227,9 +259,8 @@ from axonflow import AxonFlow, Mode, RetryConfig
227
259
 
228
260
  client = AxonFlow(
229
261
  endpoint="https://your-agent.axonflow.com",
230
- client_id="your-client-id",
231
- client_secret="your-client-secret",
232
- license_key="optional-license-key", # For enterprise features
262
+ client_id="your-client-id", # Required for enterprise features
263
+ client_secret="your-client-secret", # Required for enterprise features
233
264
  mode=Mode.PRODUCTION, # or Mode.SANDBOX
234
265
  debug=True, # Enable debug logging
235
266
  timeout=60.0, # Request timeout in seconds
@@ -305,12 +336,102 @@ ruff format .
305
336
  mypy axonflow
306
337
  ```
307
338
 
339
+ ## Examples
340
+
341
+ Complete working examples for all features are available in the [examples folder](https://github.com/getaxonflow/axonflow/tree/main/examples).
342
+
343
+ ### Community Features
344
+
345
+ ```python
346
+ # PII Detection - Automatically detect sensitive data
347
+ result = await client.get_policy_approved_context(
348
+ user_token="user-123",
349
+ query="My SSN is 123-45-6789"
350
+ )
351
+ # result.approved = True, result.requires_redaction = True (SSN detected)
352
+
353
+ # SQL Injection Detection - Block malicious queries
354
+ result = await client.get_policy_approved_context(
355
+ user_token="user-123",
356
+ query="SELECT * FROM users; DROP TABLE users;"
357
+ )
358
+ # result.approved = False, result.block_reason = "SQL injection detected"
359
+
360
+ # Static Policies - List and manage built-in policies
361
+ policies = await client.list_policies()
362
+ # Returns: [Policy(name="pii-detection", enabled=True), ...]
363
+
364
+ # Dynamic Policies - Create runtime policies
365
+ await client.create_dynamic_policy(
366
+ name="block-competitor-queries",
367
+ conditions={"contains": ["competitor", "pricing"]},
368
+ action="block"
369
+ )
370
+
371
+ # MCP Connectors - Query external data sources
372
+ resp = await client.query_connector(
373
+ user_token="user-123",
374
+ connector_name="postgres-db",
375
+ operation="query",
376
+ params={"sql": "SELECT name FROM customers"}
377
+ )
378
+
379
+ # Multi-Agent Planning - Orchestrate complex workflows
380
+ plan = await client.generate_plan(
381
+ query="Research AI governance regulations",
382
+ domain="legal"
383
+ )
384
+ result = await client.execute_plan(plan.plan_id)
385
+
386
+ # Audit Logging - Track all LLM interactions
387
+ await client.audit_llm_call(
388
+ context_id=ctx.context_id,
389
+ response_summary="AI response summary",
390
+ provider="openai",
391
+ model="gpt-4",
392
+ token_usage=TokenUsage(prompt_tokens=100, completion_tokens=200, total_tokens=300),
393
+ latency_ms=450
394
+ )
395
+ ```
396
+
397
+ ### Enterprise Features
398
+
399
+ These features require an AxonFlow Enterprise license:
400
+
401
+ ```python
402
+ # Code Governance - Automated PR reviews with AI
403
+ pr_result = await client.review_pull_request(
404
+ repo_owner="your-org",
405
+ repo_name="your-repo",
406
+ pr_number=123,
407
+ check_types=["security", "style", "performance"]
408
+ )
409
+
410
+ # Cost Controls - Budget management for LLM usage
411
+ budget = await client.get_budget("team-engineering")
412
+ # Returns: Budget(limit=1000.00, used=234.56, remaining=765.44)
413
+
414
+ # MCP Policy Enforcement - Automatic PII redaction in connector responses
415
+ resp = await client.query_connector("user", "postgres", "SELECT * FROM customers", {})
416
+ # resp.policy_info.redacted = True
417
+ # resp.policy_info.redacted_fields = ["ssn", "credit_card"]
418
+ ```
419
+
420
+ For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflow.com).
421
+
308
422
  ## Documentation
309
423
 
310
424
  - [Getting Started](https://docs.getaxonflow.com/sdk/python-getting-started)
311
425
  - [Gateway Mode Guide](https://docs.getaxonflow.com/sdk/gateway-mode)
312
- - [Examples](https://github.com/getaxonflow/axonflow/tree/main/examples)
426
+
427
+ ## Support
428
+
429
+ - **Documentation**: https://docs.getaxonflow.com
430
+ - **Issues**: https://github.com/getaxonflow/axonflow-sdk-python/issues
431
+ - **Email**: dev@getaxonflow.com
313
432
 
314
433
  ## License
315
434
 
316
435
  MIT - See [LICENSE](LICENSE) for details.
436
+
437
+ <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=fbda6e64-1812-428b-b135-ed2b548ce50d" />
@@ -143,6 +143,38 @@ result = await client.query_connector(
143
143
  )
144
144
  ```
145
145
 
146
+ ### MCP Policy Features (v3.2.0)
147
+
148
+ **Exfiltration Detection** - Prevent large-scale data extraction:
149
+
150
+ ```python
151
+ # Query with exfiltration limits (default: 10K rows, 10MB)
152
+ result = await client.query_connector(
153
+ user_token="user-jwt",
154
+ connector_name="postgres",
155
+ operation="query",
156
+ params={"sql": "SELECT * FROM customers"}
157
+ )
158
+
159
+ # Check exfiltration info
160
+ if result.policy_info.exfiltration_check.exceeded:
161
+ print(f"Limit exceeded: {result.policy_info.exfiltration_check.limit_type}")
162
+
163
+ # Configure: MCP_MAX_ROWS_PER_QUERY=1000, MCP_MAX_BYTES_PER_QUERY=5242880
164
+ ```
165
+
166
+ **Dynamic Policy Evaluation** - Orchestrator-based rate limiting, budget controls:
167
+
168
+ ```python
169
+ # Response includes dynamic policy info when enabled
170
+ if result.policy_info.dynamic_policy_info.orchestrator_reachable:
171
+ print(f"Policies evaluated: {result.policy_info.dynamic_policy_info.policies_evaluated}")
172
+ for policy in result.policy_info.dynamic_policy_info.matched_policies:
173
+ print(f" {policy.policy_name}: {policy.action}")
174
+
175
+ # Enable: MCP_DYNAMIC_POLICIES_ENABLED=true
176
+ ```
177
+
146
178
  ### Multi-Agent Planning
147
179
 
148
180
  Generate and execute multi-agent plans:
@@ -168,9 +200,8 @@ from axonflow import AxonFlow, Mode, RetryConfig
168
200
 
169
201
  client = AxonFlow(
170
202
  endpoint="https://your-agent.axonflow.com",
171
- client_id="your-client-id",
172
- client_secret="your-client-secret",
173
- license_key="optional-license-key", # For enterprise features
203
+ client_id="your-client-id", # Required for enterprise features
204
+ client_secret="your-client-secret", # Required for enterprise features
174
205
  mode=Mode.PRODUCTION, # or Mode.SANDBOX
175
206
  debug=True, # Enable debug logging
176
207
  timeout=60.0, # Request timeout in seconds
@@ -246,12 +277,102 @@ ruff format .
246
277
  mypy axonflow
247
278
  ```
248
279
 
280
+ ## Examples
281
+
282
+ Complete working examples for all features are available in the [examples folder](https://github.com/getaxonflow/axonflow/tree/main/examples).
283
+
284
+ ### Community Features
285
+
286
+ ```python
287
+ # PII Detection - Automatically detect sensitive data
288
+ result = await client.get_policy_approved_context(
289
+ user_token="user-123",
290
+ query="My SSN is 123-45-6789"
291
+ )
292
+ # result.approved = True, result.requires_redaction = True (SSN detected)
293
+
294
+ # SQL Injection Detection - Block malicious queries
295
+ result = await client.get_policy_approved_context(
296
+ user_token="user-123",
297
+ query="SELECT * FROM users; DROP TABLE users;"
298
+ )
299
+ # result.approved = False, result.block_reason = "SQL injection detected"
300
+
301
+ # Static Policies - List and manage built-in policies
302
+ policies = await client.list_policies()
303
+ # Returns: [Policy(name="pii-detection", enabled=True), ...]
304
+
305
+ # Dynamic Policies - Create runtime policies
306
+ await client.create_dynamic_policy(
307
+ name="block-competitor-queries",
308
+ conditions={"contains": ["competitor", "pricing"]},
309
+ action="block"
310
+ )
311
+
312
+ # MCP Connectors - Query external data sources
313
+ resp = await client.query_connector(
314
+ user_token="user-123",
315
+ connector_name="postgres-db",
316
+ operation="query",
317
+ params={"sql": "SELECT name FROM customers"}
318
+ )
319
+
320
+ # Multi-Agent Planning - Orchestrate complex workflows
321
+ plan = await client.generate_plan(
322
+ query="Research AI governance regulations",
323
+ domain="legal"
324
+ )
325
+ result = await client.execute_plan(plan.plan_id)
326
+
327
+ # Audit Logging - Track all LLM interactions
328
+ await client.audit_llm_call(
329
+ context_id=ctx.context_id,
330
+ response_summary="AI response summary",
331
+ provider="openai",
332
+ model="gpt-4",
333
+ token_usage=TokenUsage(prompt_tokens=100, completion_tokens=200, total_tokens=300),
334
+ latency_ms=450
335
+ )
336
+ ```
337
+
338
+ ### Enterprise Features
339
+
340
+ These features require an AxonFlow Enterprise license:
341
+
342
+ ```python
343
+ # Code Governance - Automated PR reviews with AI
344
+ pr_result = await client.review_pull_request(
345
+ repo_owner="your-org",
346
+ repo_name="your-repo",
347
+ pr_number=123,
348
+ check_types=["security", "style", "performance"]
349
+ )
350
+
351
+ # Cost Controls - Budget management for LLM usage
352
+ budget = await client.get_budget("team-engineering")
353
+ # Returns: Budget(limit=1000.00, used=234.56, remaining=765.44)
354
+
355
+ # MCP Policy Enforcement - Automatic PII redaction in connector responses
356
+ resp = await client.query_connector("user", "postgres", "SELECT * FROM customers", {})
357
+ # resp.policy_info.redacted = True
358
+ # resp.policy_info.redacted_fields = ["ssn", "credit_card"]
359
+ ```
360
+
361
+ For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflow.com).
362
+
249
363
  ## Documentation
250
364
 
251
365
  - [Getting Started](https://docs.getaxonflow.com/sdk/python-getting-started)
252
366
  - [Gateway Mode Guide](https://docs.getaxonflow.com/sdk/gateway-mode)
253
- - [Examples](https://github.com/getaxonflow/axonflow/tree/main/examples)
367
+
368
+ ## Support
369
+
370
+ - **Documentation**: https://docs.getaxonflow.com
371
+ - **Issues**: https://github.com/getaxonflow/axonflow-sdk-python/issues
372
+ - **Email**: dev@getaxonflow.com
254
373
 
255
374
  ## License
256
375
 
257
376
  MIT - See [LICENSE](LICENSE) for details.
377
+
378
+ <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=fbda6e64-1812-428b-b135-ed2b548ce50d" />
@@ -128,7 +128,7 @@ from axonflow.types import (
128
128
  UsageSummary,
129
129
  )
130
130
 
131
- __version__ = "0.6.0"
131
+ __version__ = "1.2.0"
132
132
  __all__ = [
133
133
  # Main client
134
134
  "AxonFlow",
@@ -24,6 +24,7 @@ Example:
24
24
  from __future__ import annotations
25
25
 
26
26
  import asyncio
27
+ import base64
27
28
  import concurrent.futures
28
29
  import contextlib
29
30
  import hashlib
@@ -63,6 +64,7 @@ from axonflow.exceptions import (
63
64
  AuthenticationError,
64
65
  AxonFlowError,
65
66
  ConnectionError,
67
+ ConnectorError,
66
68
  PolicyViolationError,
67
69
  TimeoutError,
68
70
  )
@@ -102,6 +104,7 @@ from axonflow.types import (
102
104
  ConnectorHealthStatus,
103
105
  ConnectorInstallRequest,
104
106
  ConnectorMetadata,
107
+ ConnectorPolicyInfo,
105
108
  ConnectorResponse,
106
109
  CreateBudgetRequest,
107
110
  ExecutionDetail,
@@ -182,7 +185,6 @@ class AxonFlow:
182
185
  client_id: str | None = None,
183
186
  client_secret: str | None = None,
184
187
  *,
185
- license_key: str | None = None,
186
188
  mode: Mode | str = Mode.PRODUCTION,
187
189
  debug: bool = False,
188
190
  timeout: float = 60.0,
@@ -199,7 +201,6 @@ class AxonFlow:
199
201
  endpoint: AxonFlow endpoint URL. Can also be set via AXONFLOW_AGENT_URL env var.
200
202
  client_id: Client ID (optional for community/self-hosted mode)
201
203
  client_secret: Client secret (optional for community/self-hosted mode)
202
- license_key: Optional license key for organization-level auth
203
204
  mode: Operation mode (production or sandbox)
204
205
  debug: Enable debug logging
205
206
  timeout: Request timeout in seconds
@@ -230,7 +231,6 @@ class AxonFlow:
230
231
  endpoint=resolved_endpoint.rstrip("/"),
231
232
  client_id=client_id,
232
233
  client_secret=client_secret,
233
- license_key=license_key,
234
234
  mode=mode,
235
235
  debug=debug,
236
236
  timeout=timeout,
@@ -247,14 +247,16 @@ class AxonFlow:
247
247
  headers: dict[str, str] = {
248
248
  "Content-Type": "application/json",
249
249
  }
250
- # Add authentication headers only when credentials are provided
251
- # For community/self-hosted mode, these can be omitted
252
- if client_secret:
253
- headers["X-Client-Secret"] = client_secret
250
+ # Add authentication and tenant headers
251
+ # client_id is always required for policy APIs (sets X-Tenant-ID)
252
+ # client_secret is optional for community mode but required for enterprise
254
253
  if client_id:
255
254
  headers["X-Tenant-ID"] = client_id # client_id is used as tenant ID for policy APIs
256
- if license_key:
257
- headers["X-License-Key"] = license_key
255
+ # OAuth2-style: Authorization: Basic base64(clientId:clientSecret)
256
+ if client_secret:
257
+ credentials = f"{client_id}:{client_secret}"
258
+ encoded = base64.b64encode(credentials.encode()).decode()
259
+ headers["Authorization"] = f"Basic {encoded}"
258
260
 
259
261
  # Initialize HTTP client
260
262
  self._http_client = httpx.AsyncClient(
@@ -298,27 +300,28 @@ class AxonFlow:
298
300
  def _has_credentials(self) -> bool:
299
301
  """Check if credentials are configured.
300
302
 
301
- Returns True if either a license key or client secret is set.
302
- These credentials are optional for community/self-hosted deployments,
303
- but required for enterprise features like Gateway Mode.
303
+ Returns True if client_id is set.
304
+ client_secret is optional for community mode but required for enterprise.
304
305
  """
305
- return bool(self._config.client_secret or self._config.license_key)
306
+ return bool(self._config.client_id)
306
307
 
307
308
  def _require_credentials(self, feature: str) -> None:
308
309
  """Require credentials for enterprise features.
309
310
 
310
- Raises AuthenticationError if no credentials are configured.
311
+ Raises AuthenticationError if client_id is not configured.
312
+ Note: client_secret is optional for community mode.
311
313
 
312
314
  Args:
313
315
  feature: Name of the feature requiring credentials (for error message)
314
316
 
315
317
  Raises:
316
- AuthenticationError: If no credentials are configured
318
+ AuthenticationError: If client_id is not configured
317
319
  """
318
320
  if not self._has_credentials():
319
321
  msg = (
320
- f"{feature} requires credentials. "
321
- "Set client_secret or license_key when creating the client."
322
+ f"{feature} requires client_id. "
323
+ "Set client_id when creating the client "
324
+ "(client_secret is optional for community mode)."
322
325
  )
323
326
  raise AuthenticationError(msg)
324
327
 
@@ -362,19 +365,20 @@ class AxonFlow:
362
365
  return SyncAxonFlow(cls(endpoint, client_id, client_secret, **kwargs))
363
366
 
364
367
  @classmethod
365
- def sandbox(cls, api_key: str = "demo-key") -> AxonFlow:
368
+ def sandbox(cls, client_id: str = "demo-key", client_secret: str = "demo-key") -> AxonFlow: # noqa: S107
366
369
  """Create a sandbox client for testing.
367
370
 
368
371
  Args:
369
- api_key: Optional API key (defaults to demo-key)
372
+ client_id: Optional client ID (defaults to demo-key)
373
+ client_secret: Optional client secret (defaults to demo-key)
370
374
 
371
375
  Returns:
372
376
  Configured AxonFlow client for sandbox environment
373
377
  """
374
378
  return cls(
375
379
  endpoint="https://staging-eu.getaxonflow.com",
376
- client_id=api_key,
377
- client_secret=api_key,
380
+ client_id=client_id,
381
+ client_secret=client_secret,
378
382
  mode=Mode.SANDBOX,
379
383
  debug=True,
380
384
  )
@@ -543,7 +547,8 @@ class AxonFlow:
543
547
  """Execute a query through AxonFlow with policy enforcement.
544
548
 
545
549
  Args:
546
- user_token: User authentication token
550
+ user_token: User authentication token. If empty, defaults to "anonymous"
551
+ for audit purposes (community mode).
547
552
  query: The query or prompt
548
553
  request_type: Type of request (chat, sql, mcp-query, multi-agent-plan)
549
554
  context: Optional additional context
@@ -556,6 +561,10 @@ class AxonFlow:
556
561
  AuthenticationError: If credentials are invalid
557
562
  TimeoutError: If request times out
558
563
  """
564
+ # Default to "anonymous" if user_token is empty (community mode)
565
+ if not user_token:
566
+ user_token = "anonymous" # noqa: S105 - not a password, just a placeholder
567
+
559
568
  # Check cache
560
569
  if self._cache is not None:
561
570
  cache_key = self._get_cache_key(request_type, query, user_token)
@@ -743,6 +752,98 @@ class AxonFlow:
743
752
  },
744
753
  )
745
754
 
755
+ async def mcp_query(
756
+ self,
757
+ connector: str,
758
+ statement: str,
759
+ options: dict[str, Any] | None = None,
760
+ ) -> ConnectorResponse:
761
+ """Execute a query directly against the MCP connector endpoint.
762
+
763
+ This method calls the agent's /mcp/resources/query endpoint which provides:
764
+ - Request-phase policy evaluation (SQLi blocking, PII blocking)
765
+ - Response-phase policy evaluation (PII redaction)
766
+ - PolicyInfo metadata in responses
767
+
768
+ Args:
769
+ connector: Name of the MCP connector (e.g., "postgres")
770
+ statement: SQL statement or query to execute
771
+ options: Optional additional options for the query
772
+
773
+ Returns:
774
+ ConnectorResponse with data, redaction info, and policy_info
775
+
776
+ Raises:
777
+ ConnectorError: If the request is blocked by policy or fails
778
+
779
+ Example:
780
+ response = await client.mcp_query(
781
+ connector="postgres",
782
+ statement="SELECT * FROM customers LIMIT 10"
783
+ )
784
+ if response.redacted:
785
+ print(f"Redacted fields: {response.redacted_fields}")
786
+ """
787
+ if not connector:
788
+ msg = "connector name is required"
789
+ raise ConnectorError(msg, connector=None, operation="mcp_query")
790
+ if not statement:
791
+ msg = "statement is required"
792
+ raise ConnectorError(msg, connector=connector, operation="mcp_query")
793
+
794
+ url = f"{self._config.endpoint}/mcp/resources/query"
795
+ body = {
796
+ "connector": connector,
797
+ "statement": statement,
798
+ "options": options or {},
799
+ }
800
+
801
+ if self._config.debug:
802
+ self._logger.debug("MCP Query", connector=connector, statement=statement[:50])
803
+
804
+ response = await self._http_client.post(url, json=body)
805
+ response_data = response.json()
806
+
807
+ # Handle policy blocks (403 responses)
808
+ if not response.is_success:
809
+ error_msg = response_data.get("error", f"MCP query failed: {response.status_code}")
810
+ raise ConnectorError(error_msg, connector=connector, operation="mcp_query")
811
+
812
+ if self._config.debug:
813
+ self._logger.debug(
814
+ "MCP Query result",
815
+ connector=connector,
816
+ success=response_data.get("success"),
817
+ redacted=response_data.get("redacted"),
818
+ )
819
+
820
+ # Build policy_info if present
821
+ policy_info = None
822
+ if response_data.get("policy_info"):
823
+ policy_info = ConnectorPolicyInfo.model_validate(response_data["policy_info"])
824
+
825
+ return ConnectorResponse(
826
+ success=response_data.get("success", True),
827
+ data=response_data.get("data"),
828
+ error=response_data.get("error"),
829
+ meta=response_data.get("meta", {}),
830
+ redacted=response_data.get("redacted", False),
831
+ redacted_fields=response_data.get("redacted_fields", []),
832
+ policy_info=policy_info,
833
+ )
834
+
835
+ async def mcp_execute(
836
+ self,
837
+ connector: str,
838
+ statement: str,
839
+ options: dict[str, Any] | None = None,
840
+ ) -> ConnectorResponse:
841
+ """Execute a statement against an MCP connector (alias for mcp_query).
842
+
843
+ Same as mcp_query but follows the naming convention of other execute* methods.
844
+ """
845
+ return await self.mcp_query(connector, statement, options)
846
+
746
847
  async def generate_plan(
747
848
  self,
748
849
  query: str,
@@ -886,7 +987,7 @@ class AxonFlow:
886
987
  Returns:
887
988
  PlanExecutionResponse with current status
888
989
  """
889
- response = await self._request("GET", f"/api/plans/{plan_id}")
990
+ response = await self._request("GET", f"/api/v1/plan/{plan_id}")
890
991
  return PlanExecutionResponse.model_validate(response)
891
992
 
892
993
  # =========================================================================
@@ -907,7 +1008,7 @@ class AxonFlow:
907
1008
 
908
1009
  Note:
909
1010
  This is an enterprise feature that requires credentials.
910
- Set client_secret or license_key when creating the client.
1011
+ Set client_id and client_secret when creating the client.
911
1012
 
912
1013
  Args:
913
1014
  user_token: JWT token for the user making the request
@@ -1036,7 +1137,7 @@ class AxonFlow:
1036
1137
 
1037
1138
  Note:
1038
1139
  This is an enterprise feature that requires credentials.
1039
- Set client_secret or license_key when creating the client.
1140
+ Set client_id and client_secret when creating the client.
1040
1141
 
1041
1142
  Args:
1042
1143
  context_id: Context ID from get_policy_approved_context()
@@ -1731,7 +1832,7 @@ class AxonFlow:
1731
1832
  self._logger.debug("Toggling dynamic policy", policy_id=policy_id, enabled=enabled)
1732
1833
 
1733
1834
  response = await self._orchestrator_request(
1734
- "PATCH",
1835
+ "PUT",
1735
1836
  f"/api/v1/dynamic-policies/{policy_id}",
1736
1837
  json_data={"enabled": enabled},
1737
1838
  )
@@ -1992,6 +2093,29 @@ class AxonFlow:
1992
2093
  response = await self._portal_request("POST", f"/api/v1/code-governance/prs/{pr_id}/sync")
1993
2094
  return PRRecord.model_validate(response)
1994
2095
 
2096
+ async def close_pr(self, pr_id: str, delete_branch: bool = True) -> PRRecord:
2097
+ """Close a PR without merging and optionally delete the branch.
2098
+
2099
+ This is an enterprise feature for cleaning up test/demo PRs.
2100
+ Supports all Git providers: GitHub, GitLab, Bitbucket.
2101
+
2102
+ Args:
2103
+ pr_id: PR record ID
2104
+ delete_branch: Whether to delete the source branch (default: True)
2105
+
2106
+ Returns:
2107
+ Closed PR record
2108
+ """
2109
+ if self._config.debug:
2110
+ self._logger.debug("Closing PR", pr_id=pr_id, delete_branch=delete_branch)
2111
+
2112
+ path = f"/api/v1/code-governance/prs/{pr_id}"
2113
+ if delete_branch:
2114
+ path += "?delete_branch=true"
2115
+
2116
+ response = await self._portal_request("DELETE", path)
2117
+ return PRRecord.model_validate(response)
2118
+
1995
2119
  # =========================================================================
1996
2120
  # Code Governance Metrics and Export
1997
2121
  # =========================================================================
@@ -2796,7 +2920,10 @@ class SyncAxonFlow:
2796
2920
  request_type: str,
2797
2921
  context: dict[str, Any] | None = None,
2798
2922
  ) -> ClientResponse:
2799
- """Execute a query through AxonFlow."""
2923
+ """Execute a query through AxonFlow.
2924
+
2925
+ If user_token is empty, defaults to "anonymous" for audit purposes.
2926
+ """
2800
2927
  return self._run_sync(
2801
2928
  self._async_client.execute_query(user_token, query, request_type, context)
2802
2929
  )
@@ -2833,6 +2960,30 @@ class SyncAxonFlow:
2833
2960
  self._async_client.query_connector(user_token, connector_name, operation, params)
2834
2961
  )
2835
2962
 
2963
+ def mcp_query(
2964
+ self,
2965
+ connector: str,
2966
+ statement: str,
2967
+ options: dict[str, Any] | None = None,
2968
+ ) -> ConnectorResponse:
2969
+ """Execute a query directly against the MCP connector endpoint.
2970
+
2971
+ This method calls the agent's /mcp/resources/query endpoint which provides:
2972
+ - Request-phase policy evaluation (SQLi blocking, PII blocking)
2973
+ - Response-phase policy evaluation (PII redaction)
2974
+ - PolicyInfo metadata in responses
2975
+ """
2976
+ return self._run_sync(self._async_client.mcp_query(connector, statement, options))
2977
+
2978
+ def mcp_execute(
2979
+ self,
2980
+ connector: str,
2981
+ statement: str,
2982
+ options: dict[str, Any] | None = None,
2983
+ ) -> ConnectorResponse:
2984
+ """Execute a statement against an MCP connector (alias for mcp_query)."""
2985
+ return self._run_sync(self._async_client.mcp_execute(connector, statement, options))
2986
+
2836
2987
  def generate_plan(
2837
2988
  self,
2838
2989
  query: str,
@@ -3084,6 +3235,10 @@ class SyncAxonFlow:
3084
3235
  """Sync PR status with the Git provider."""
3085
3236
  return self._run_sync(self._async_client.sync_pr_status(pr_id))
3086
3237
 
3238
+ def close_pr(self, pr_id: str, delete_branch: bool = True) -> PRRecord:
3239
+ """Close a PR without merging and optionally delete the branch."""
3240
+ return self._run_sync(self._async_client.close_pr(pr_id, delete_branch))
3241
+
3087
3242
  def get_code_governance_metrics(self) -> CodeGovernanceMetrics:
3088
3243
  """Get aggregated code governance metrics."""
3089
3244
  return self._run_sync(self._async_client.get_code_governance_metrics())