fix mcp tool guardrails

This commit is contained in:
mubashir1osmani 2026-04-07 07:09:29 -04:00
parent d251238bd7
commit 88fab5d280
No known key found for this signature in database
GPG key ID: AB055FF67D0B4D9A
4 changed files with 56 additions and 3 deletions

View file

@ -2174,7 +2174,14 @@ class MCPServerManager:
GuardrailRaisedException,
HTTPException,
) as e:
# Re-raise guardrail exceptions to properly fail the MCP call
# Re-raise guardrail exceptions to properly fail the MCP call.
# Attach guardrail logging info so _execute_tool_calls can surface it
# in observability (UI logs, Langfuse, DataDog, etc.).
guardrail_info = synthetic_llm_data.get("metadata", {}).get(
"standard_logging_guardrail_information"
)
if guardrail_info:
e._guardrail_logging_info = guardrail_info # type: ignore[attr-defined]
verbose_logger.error(f"Guardrail blocked MCP tool call pre call: {str(e)}")
raise e

View file

@ -56,6 +56,8 @@ class ToolPermissionGuardrail(CustomGuardrail):
kwargs["supported_event_hooks"] = [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.pre_mcp_call,
GuardrailEventHooks.during_mcp_call,
]
super().__init__(**kwargs)
@ -515,9 +517,27 @@ class ToolPermissionGuardrail(CustomGuardrail):
add_guardrail_to_applied_guardrails_header,
)
event_type: GuardrailEventHooks = GuardrailEventHooks.pre_call
# pre_mcp_call path: synthetic data has mcp_tool_name but no tools list
mcp_tool_name: Optional[str] = data.get("mcp_tool_name")
event_type: GuardrailEventHooks = (
GuardrailEventHooks.pre_mcp_call
if mcp_tool_name is not None
else GuardrailEventHooks.pre_call
)
if self.should_run_guardrail(data=data, event_type=event_type) is not True:
return data
if mcp_tool_name is not None:
is_allowed, _, message = self._check_tool_permission(mcp_tool_name)
if not is_allowed and message is not None:
verbose_proxy_logger.warning(f"Tool Permission Guardrail: {message}")
raise HTTPException(
status_code=400,
detail={
"error": "Violated guardrail policy",
"detection_message": message,
},
)
return data
new_tools: Optional[List[ChatCompletionToolParam]] = data.get("tools")
if new_tools is None:

View file

@ -546,6 +546,17 @@ class ProxyLogging:
role="user", content=tool_call_content
)
# Build namespaced tool name ({server_name}-{tool_name}) so guardrail rules
# written against the prefixed form (e.g. "exa-.*") match correctly.
server_name = kwargs.get("server_name") or getattr(
request_obj, "server_name", None
)
namespaced_tool_name = (
f"{server_name}-{request_obj.tool_name}"
if server_name
else request_obj.tool_name
)
# Create synthetic LLM data that guardrails can process
synthetic_data = {
"messages": [synthetic_message],
@ -555,7 +566,7 @@ class ProxyLogging:
"user_api_key_end_user_id": kwargs.get("user_api_key_end_user_id"),
"user_api_key_hash": kwargs.get("user_api_key_hash"),
"user_api_key_request_route": kwargs.get("user_api_key_request_route"),
"mcp_tool_name": request_obj.tool_name, # Keep original for reference
"mcp_tool_name": namespaced_tool_name, # namespaced {server}-{tool} for rule matching
"mcp_arguments": request_obj.arguments, # Keep original for reference
# Raw Bearer token from the original HTTP request — allows guardrails
# (e.g. MCPJWTSigner) to independently verify the caller's identity

View file

@ -849,6 +849,11 @@ class LiteLLM_Proxy_MCP_Handler:
)
except BlockedPiiEntityError as e:
_guardrail_info = getattr(e, "_guardrail_logging_info", None)
if _guardrail_info:
logging_request_data.setdefault("metadata", {})[
"standard_logging_guardrail_information"
] = _guardrail_info
await LiteLLM_Proxy_MCP_Handler._log_mcp_tool_failure(
proxy_logging_obj=proxy_logging_obj,
user_api_key_auth=user_api_key_auth,
@ -867,6 +872,11 @@ class LiteLLM_Proxy_MCP_Handler:
}
)
except GuardrailRaisedException as e:
_guardrail_info = getattr(e, "_guardrail_logging_info", None)
if _guardrail_info:
logging_request_data.setdefault("metadata", {})[
"standard_logging_guardrail_information"
] = _guardrail_info
await LiteLLM_Proxy_MCP_Handler._log_mcp_tool_failure(
proxy_logging_obj=proxy_logging_obj,
user_api_key_auth=user_api_key_auth,
@ -885,6 +895,11 @@ class LiteLLM_Proxy_MCP_Handler:
}
)
except HTTPException as e:
_guardrail_info = getattr(e, "_guardrail_logging_info", None)
if _guardrail_info:
logging_request_data.setdefault("metadata", {})[
"standard_logging_guardrail_information"
] = _guardrail_info
await LiteLLM_Proxy_MCP_Handler._log_mcp_tool_failure(
proxy_logging_obj=proxy_logging_obj,
user_api_key_auth=user_api_key_auth,