From 8352c425a1afdf46ba949eba7184e2f3e414e922 Mon Sep 17 00:00:00 2001 From: Jim Clark Date: Tue, 16 Sep 2025 13:58:16 -0700 Subject: [PATCH 1/2] Add request to marshalled data --- .../internal/interceptors/interceptors.go | 4 +- docs/interceptors.md | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 docs/interceptors.md diff --git a/cmd/docker-mcp/internal/interceptors/interceptors.go b/cmd/docker-mcp/internal/interceptors/interceptors.go index 22138b1c..75749a56 100644 --- a/cmd/docker-mcp/internal/interceptors/interceptors.go +++ b/cmd/docker-mcp/internal/interceptors/interceptors.go @@ -117,7 +117,9 @@ func (i *Interceptor) ToMiddleware() mcp.Middleware { response, err := next(ctx, method, req) if i.When == "after" { - message, err := json.Marshal(response) + message, err := json.Marshal( + map[string]any{"request": req, + "response": response}) if err != nil { return nil, fmt.Errorf("marshalling response: %w", err) } diff --git a/docs/interceptors.md b/docs/interceptors.md new file mode 100644 index 00000000..d16f70eb --- /dev/null +++ b/docs/interceptors.md @@ -0,0 +1,55 @@ +# Interceptors (beta) + +Interceptors are extension points. They can mediate tool calls for any active MCP running in the gateway. This feature should be considered beta, and subject to change as we learn from users what they need to manage their MCP workloads. + +## **After** Tool Calls + +A simple method to experiment with interceptors is to register a script to run after a tool call has completed but before the response is sent back to the client. Start the gateway with the `--interceptor` argument and provide a local script. + +```bash +docker mcp gateway run --interceptor "after:exec:$HOME/script.sh" +``` + +This would not make a good production configuration as it relies on an external script but this is a useful way to try out an interceptor on real MCP traffic. The interceptor script will receive a json payload on stdin, and _must_ return the possibly edited response payload on stdout. Here's a simple that just echos the response. + +```bash +#!/bin/bash + +# Read JSON from stdin into a variable +json_input=$(cat) + +# Extract the response property and serialize it back to JSON +echo "$json_input" | jq -r '.response | tostring' +``` + +The incoming request will have both `request` and `response` properties. + +``` +{ + "request": { + "Session": {}, + "Params": { + "_meta": { + "progressToken": 1 + }, + "name": "get_me", + "arguments": {} + }, + "Extra": null + }, + "response": { + "content": [ + { + "type": "text", + "text": "..." + } + ] + } +} + +``` + +The interceptor must return a valid `ToolCall` response. Interceptors can easily blow your entire MCP workload. They can edit the response and are thus very powerful. + +## **before** Tool Calls + From 29d1942d4ad07abcaeea7af997e73b967c5e03e0 Mon Sep 17 00:00:00 2001 From: Jim Clark Date: Tue, 16 Sep 2025 15:42:45 -0700 Subject: [PATCH 2/2] Add section on after interceptor --- .../internal/interceptors/interceptors.go | 6 ++++-- docs/interceptors.md | 21 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cmd/docker-mcp/internal/interceptors/interceptors.go b/cmd/docker-mcp/internal/interceptors/interceptors.go index 75749a56..543d2660 100644 --- a/cmd/docker-mcp/internal/interceptors/interceptors.go +++ b/cmd/docker-mcp/internal/interceptors/interceptors.go @@ -118,8 +118,10 @@ func (i *Interceptor) ToMiddleware() mcp.Middleware { if i.When == "after" { message, err := json.Marshal( - map[string]any{"request": req, - "response": response}) + map[string]any{ + "request": req, + "response": response, + }) if err != nil { return nil, fmt.Errorf("marshalling response: %w", err) } diff --git a/docs/interceptors.md b/docs/interceptors.md index d16f70eb..b156eeeb 100644 --- a/docs/interceptors.md +++ b/docs/interceptors.md @@ -2,7 +2,7 @@ Interceptors are extension points. They can mediate tool calls for any active MCP running in the gateway. This feature should be considered beta, and subject to change as we learn from users what they need to manage their MCP workloads. -## **After** Tool Calls +## **after** Tool Calls A simple method to experiment with interceptors is to register a script to run after a tool call has completed but before the response is sent back to the client. Start the gateway with the `--interceptor` argument and provide a local script. @@ -24,7 +24,7 @@ echo "$json_input" | jq -r '.response | tostring' The incoming request will have both `request` and `response` properties. -``` +```json { "request": { "Session": {}, @@ -49,7 +49,22 @@ The incoming request will have both `request` and `response` properties. ``` -The interceptor must return a valid `ToolCall` response. Interceptors can easily blow your entire MCP workload. They can edit the response and are thus very powerful. +The stdout must either be empty or it must be able to be parsed to a valid `ToolCall` response. These interceptors are powerful. They can easily blow up your entire MCP workload. + +* we do not currently care about exit codes +* stdout must contain valid `application/json` that can be parsed to a ToolCall response +* if the script writes nothing to stdout, the existing response will be passed on. ## **before** Tool Calls +Analogously, run with a before interceptor. + +```bash +docker mcp gateway run --interceptor "before:exec:$HOME/script.sh" +``` + +For _before_ interceptors, only the request value will written to stdin. Before interceptors are potentially more impactful than _after_ interceptors. If they return anything on stdout then two things will happen. + +1. the handler chain will prematurely end (no tool call will be made) +2. the response payload from the interceptor will _become_ the tool call response. This means that _before_ interceptors can reject tool calls and add their own tool call responses. +