Skip to content

Add a stub MCP upstream test harness for end-to-end /mcp output-path tests #674

Description

@moonming

Context

The /mcp handler (crates/aisix-proxy/src/mcp.rs) governs a tools/call with the same pipeline as an LLM request: per-tool ACL, rate-limit + budget, input + output guardrails, and a usage event. The input path is testable end-to-end through the router because an input guardrail blocks before any upstream is contacted (input_guardrail_blocks_tool_call_with_forbidden_args drives the real router and asserts the JSON-RPC error envelope + echoed id).

The output path cannot be driven end-to-end today: scanning the tool result happens after the gateway dispatches to a real MCP upstream, and the proxy test harness has no in-process MCP upstream that returns a scannable result. So output-side behavior is currently covered at two levels below the full handler:

  • output_guardrail_block (the scan/decision helper) — block / allow / no-result / decoded-text-vs-envelope cases.
  • jsonrpc_guardrail_block (the wire-envelope builder both hooks funnel through) — status, code -32600, content-type, echoed id, no result.

Gap

Two seams are only covered by structural equivalence to the input path, not directly:

  1. The clean-result pass-through: a clean tool result is buffered and re-wrapped via Response::from_parts(resp_parts, Body::from(resp_bytes)). A regression that dropped/garbled headers or the body on this path would not be caught.
  2. The output-block wiring end-to-end: handler dispatches → buffers → output_guardrail_blockjsonrpc_guardrail_block(rpc_id, "tool result", …).

Proposal

Add a minimal in-process MCP upstream stub to the proxy test harness (an mcp_servers entry backed by an EphemeralBridge-style handler that echoes its arguments as a text content block). Then add router-level tests:

  • a tools/call whose result text carries a forbidden token → HTTP 200 JSON-RPC error, code -32600, echoed request id;
  • a clean tools/call → the upstream result passes through unchanged (status, content-type, body bytes intact).

Why separate

Wiring a real rmcp upstream into the proxy harness is a non-trivial test-infra addition, out of scope for the output-guardrail change itself (#671). Surfaced by independent audit of #673; tracked here so the gap is explicit rather than silent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions