Skip to content

Support tool input/output guardrails for local MCP server tools #4620

Description

@dgenio

Please read this first

  • Have you read the docs? Yes.
  • Have you searched for related issues? Yes.

Related but distinct:

This request is narrower: allow locally executed MCP tools to opt into the existing tool input/output guardrail abstraction that is already available for function tools.

Describe the feature

The SDK has a useful tool_input_guardrails / tool_output_guardrails abstraction for function tools.

Today, however, the guardrails documentation explicitly scopes tool guardrails to function tools, while local MCP servers have a different set of controls:

  • tool_filter for model-visible tool availability;
  • require_approval for HITL approval;
  • tool_meta_resolver for per-call MCP _meta;
  • failure_error_function for MCP failure handling.

All of those are useful, but they solve different problems.

There does not appear to be a public configuration point for saying:

For every invocation of this locally executed MCP tool, run these input guardrails immediately before execution and these output guardrails immediately after execution.

This is slightly surprising because local MCP tools are already converted internally into SDK FunctionTool objects by MCPUtil.

Production-shaped example

Consider an agent connected to an application-managed MCP server.

Before an MCP call executes, the application may need to deterministically check things such as:

  • whether the model-generated arguments contain data that must not be sent to that tool;
  • whether an argument violates an invariant derived from trusted run context;
  • whether that particular invocation should be rejected even though the tool itself is visible and available.

After the MCP call returns, the application may need to check the result before it becomes model-visible, for example:

  • reject a response containing sensitive/internal data;
  • prevent an unexpected or unsafe result from entering the agent context;
  • apply the same output-validation policy already used for local function tools.

Conceptually:

model chooses MCP tool
        ↓
tool input guardrails
        ↓
local MCP server.call_tool(...)
        ↓
tool output guardrails
        ↓
result becomes model-visible

We ended up implementing this kind of wrapper downstream for MCP-backed agents. What started as a sample-specific concern became shared infrastructure because the same need recurred across tools and agents.

Why the existing MCP controls are not equivalent

tool_filter is primarily about visibility / eligibility: should the model see this tool?

require_approval answers whether execution should pause for approval.

tool_meta_resolver is useful for trusted per-call metadata such as tenant or trace information.

Tool guardrails answer a separate question:

Given the exact invocation/output at this execution boundary, should it be accepted?

These mechanisms are complementary rather than alternatives.

This also means I am not proposing that client-side guardrails replace authorization in the MCP server. Resource/action authorization should still be enforced at the protected operation.

Possible API shape

The exact API is less important than parity with the existing tool-guardrail model.

One possible server-level shape would be:

server = MCPServerStreamableHttp(
    name="Billing",
    params={"url": "..."},
    tool_input_guardrails=[validate_mcp_input],
    tool_output_guardrails=[validate_mcp_output],
)

A resolver/per-tool configuration could also make sense if maintainers prefer not to apply every guardrail to every tool.

Since local MCP tools are already converted to FunctionTools internally, another possibility may simply be attaching the configured guardrails during MCP → FunctionTool conversion rather than introducing a separate execution system.

Desired semantics

Ideally:

  1. local MCP tools can opt into the existing input-tool-guardrail semantics before call_tool();
  2. local MCP tools can opt into the existing output-tool-guardrail semantics before the result is accepted/model-visible;
  3. configuration can distinguish tools when necessary, rather than requiring one policy for every tool on a server;
  4. guardrails receive normal run context plus enough MCP origin information to distinguish server/tool identity;
  5. behavior is unchanged when no MCP guardrails are configured;
  6. interaction with require_approval is documented and, where practical, follows the same ordering semantics as function-tool approvals + guardrails;
  7. guardrail results remain observable/traced in the same way as existing tool-guardrail results.

Non-goals

This issue is intentionally not asking for:

  • a new authorization framework;
  • replacing server-side MCP authorization;
  • a generic middleware architecture for every SDK tool type;
  • arbitrary input/output transformation semantics beyond what existing tool guardrails support;
  • HostedMCPTool parity in the same change.

Hosted MCP has a materially different execution boundary because the Python process does not own the MCP round trip, so I think it should be considered separately.

Why this seems like a useful SDK-level primitive

MCP tools are often precisely where an agent crosses from model reasoning into external systems.

Having the same lightweight pre/post execution checks for:

FunctionTool

and:

local MCP-backed FunctionTool

would make it easier to apply one production policy consistently without wrapping every MCP tool manually or reimplementing the MCP integration layer.

If maintainers agree this belongs in the SDK, I'd be happy to help with a focused implementation or docs/tests around the preferred API shape.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions