Skip to content

before interceptors fail to marshal request: json: unsupported type func(mcp.CloseSSEStreamArgs) #358

@bradthebeeble

Description

@bradthebeeble

Summary

When running docker mcp gateway run with a before interceptor (exec, docker, or http), the gateway can fail with:

json: unsupported type: func(mcp.CloseSSEStreamArgs)

This prevents before-interceptors from working (notably on streaming/sse transports).

Environment

  • Repo: docker/mcp-gateway
  • Version: v0.37.0 (commit cc7998a51493a26bd9e51e1f7fb25cee60fc03a5)
  • Transport: streaming (also seen on sse)
  • Host: Docker Desktop (macOS)

Steps to reproduce

  1. Start the gateway with a before exec interceptor:
docker mcp gateway run --port 8080 --transport streaming \
  --interceptor 'before:exec:echo Arguments=$(jq -r ".params.arguments") >&2'
  1. Connect a client and invoke any tool call (tools/call).

Expected behavior

  • The before interceptor receives JSON and can read .params.arguments.
  • The call proceeds (passthrough) unless the interceptor returns a non-empty JSON response.

Actual behavior

  • Gateway fails with an error like:

executing interceptor: marshalling request: json: unsupported type: func(mcp.CloseSSEStreamArgs)

Root cause

In pkg/interceptors/interceptors.go, ToMiddleware() attempts to serialize the full mcp.Request for before interceptors:

message, err := json.Marshal(req)

On streaming/SSE transports, the mcp.Request may contain internal callback functions (e.g. func(mcp.CloseSSEStreamArgs)), which are not JSON-serializable.

Proposed fix

For "before" interceptors, marshal only the serializable tool-call payload (matching the documented/observed interceptor request shape):

{ "method": "tools/call", "params": { "name": "...", "arguments": { ... } } }

Patch sketch:

var payload any
if callReq, ok := req.(*mcp.CallToolRequest); ok && callReq.Params != nil {
  payload = map[string]any{"method": "tools/call", "params": callReq.Params}
} else {
  payload = map[string]any{"method": "tools/call", "params": map[string]any{}}
}
message, err := json.Marshal(payload)

This preserves compatibility with interceptors using jq -r ".params.arguments".

Workaround

No reliable workaround other than disabling before interceptors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions