From bb80f2f8aba789e162e880ba6c27efb66dafa7cf Mon Sep 17 00:00:00 2001 From: Yossi Meloch Date: Fri, 19 Jun 2026 17:18:21 -0500 Subject: [PATCH] fix: use investigate/v2 API path for domain tools investigate_domain and get_domain_categorization called the non-existent security/v1/domains/{domain}/{risk-score,categorization} routes, which the Cisco Secure Access API gateway rejects with 404 "no Route matched". Per the Secure Access Investigate API, these live under the investigate/v2 scope with the resource before the domain: GET investigate/v2/domains/risk-score/{domain} GET investigate/v2/domains/categorization/{domain} Also URL-encode the domain before interpolating it into the path, and drop the now-unused SECURITY_SCOPE constant. Co-authored-by: Cursor --- cisco_secure_access_mcp/tools/all_tools.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cisco_secure_access_mcp/tools/all_tools.py b/cisco_secure_access_mcp/tools/all_tools.py index 0520b51..e1fc279 100644 --- a/cisco_secure_access_mcp/tools/all_tools.py +++ b/cisco_secure_access_mcp/tools/all_tools.py @@ -10,6 +10,7 @@ import time from datetime import datetime from typing import Any +from urllib.parse import quote from mcp.server.fastmcp import Context @@ -18,7 +19,7 @@ POLICIES_SCOPE = "policies/v2" REPORTS_SCOPE = "reports/v2" -SECURITY_SCOPE = "security/v1" +INVESTIGATE_SCOPE = "investigate/v2" DEPLOYMENTS_SCOPE = "deployments/v2" ORG_DESTINATION_LIMIT = 250_000 @@ -528,7 +529,8 @@ async def audit_stale_lists(ctx: Context, stale_days: int = 30) -> str: async def investigate_domain(domain: str, ctx: Context) -> str: """Get security information for a domain.""" try: - data = await _get_client(ctx).get(SECURITY_SCOPE, f"domains/{domain}/risk-score") + safe_domain = quote(domain, safe="") + data = await _get_client(ctx).get(INVESTIGATE_SCOPE, f"domains/risk-score/{safe_domain}") return _json(data) except Exception as e: return format_error(e) @@ -538,7 +540,10 @@ async def investigate_domain(domain: str, ctx: Context) -> str: async def get_domain_categorization(domain: str, ctx: Context) -> str: """Get the status and content categorization of a domain.""" try: - data = await _get_client(ctx).get(SECURITY_SCOPE, f"domains/{domain}/categorization") + safe_domain = quote(domain, safe="") + data = await _get_client(ctx).get( + INVESTIGATE_SCOPE, f"domains/categorization/{safe_domain}" + ) return _json(data) except Exception as e: return format_error(e)