feat: MCP Sampling security — bidirectional middleware pipeline - #114
Merged
Conversation
#86) Add check_response() to the Middleware trait (default: Allow) and run_response() to Pipeline. PayloadFilterMiddleware and RateLimitMiddleware implement check_response to enforce payload filtering and rate limiting on server-initiated sampling/createMessage and elicitation/create messages. McpGateway::handle_server_request is the new transport entry point: runs run_response, emits audit entries, and returns a JSON-RPC error to the server when a request is blocked. 12 new unit tests cover pipeline routing, payload blocking, injection detection, and the gateway entry point. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCP servers can initiate
sampling/createMessageandelicitation/createrequests toward the client (reverse direction: server→client). Previously these bypassed all security controls — no rate limiting, no payload filtering, no audit.This PR closes that gap with a minimal, backward-compatible architecture change:
Middlewaretrait gainscheck_response(&self, ctx) -> Decisionwith a defaultAllowimplementation — existing middlewares need zero changes.Pipelinegainsrun_response()that callscheck_responseon each middleware in order, stopping at the firstBlock.PayloadFilterMiddleware::check_responsescanssampling/createMessagemessage text andelicitation/createprompt strings for blocked patterns and injection signatures.RateLimitMiddleware::check_responsecounts server-initiated sampling requests against the agent's global rate limit (server-triggered LLM inference is billed to the agent — must be metered).McpGateway::handle_server_requestis the new transport-level entry point: runsrun_response, emits audit entries for every server-initiated message, and returns a JSON-RPC error to the server when blocked. Transport layers call this when they detect a server-initiated message in the upstream SSE stream.Closes #86
Test plan
cargo fmt --check— cleancargo clippy -- -D warnings— zero warningscargo test --lib— all 446 unit tests passrun_response_empty_pipeline_allows— empty pipeline passesrun_response_default_impl_allows— middlewares withoutcheck_responsedon't interfererun_response_blocks_when_check_response_blocks— customcheck_responsecan blockrun_response_stops_at_first_block— short-circuits correctlysampling_clean_message_allowed— benign sampling passes payload filtersampling_blocked_pattern_in_message_blocked— blocked pattern in sampling textelicitation_injection_in_prompt_blocked— injection in elicitation promptnon_sampling_method_skipped_by_check_response— non-sampling methods skippedsampling_redact_mode_does_not_block_on_block_pattern— redact mode respectedsampling_clean_message_passes(gateway) — passes through asNonesampling_blocked_pattern_returns_error(gateway) — returns JSON-RPC errorelicitation_injection_returns_error(gateway) — injection blocked at gateway level🤖 Generated with Claude Code