Skip to content

Webhook Middleware Phase 3: Mutating webhook middleware with JSONPatch #3398

@JAORMX

Description

@JAORMX

Overview

Implement mutating webhook middleware that calls external HTTP services to transform MCP requests using JSONPatch (RFC 6902). This enables organizations to enrich requests with data from external sources (HR systems, CMDB, project databases) without modifying ToolHive code.

RFC: https://github.com/stacklok/toolhive-rfcs/blob/main/rfcs/THV-0017-dynamic-webhook-middleware.md

Depends on: Phase 1 (Core webhook package)

Files to Create

File Purpose
pkg/webhook/mutating/middleware.go Mutating webhook middleware implementation
pkg/webhook/mutating/config.go Configuration types and validation
pkg/webhook/mutating/patch.go JSONPatch application and validation

Files to Modify

File Changes
pkg/runner/middleware.go Register mutating-webhook in GetSupportedMiddlewareFactories()
pkg/runner/config.go Add MutatingWebhooks []webhook.WebhookConfig field to RunConfig

Middleware Implementation

// pkg/webhook/mutating/middleware.go
const MiddlewareType = "mutating-webhook"

type MiddlewareParams struct {
    Webhooks []webhook.WebhookConfig `json:"webhooks"`
}

func CreateMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error {
    // 1. Parse params
    // 2. Create HTTP client
    // 3. Build middleware function that:
    //    - Gets parsed MCP request from context
    //    - Builds webhook request
    //    - Calls each webhook in order
    //    - Applies JSONPatch operations to mcp_request
    //    - Updates request context/body with mutated request
    //    - Handle failures per FailurePolicy
    // 4. Register with runner.AddMiddleware()
}

Request/Response Format

Request to webhook (POST):

{
  "version": "v0.1.0",
  "uid": "unique-request-id",
  "timestamp": "2025-01-22T10:30:00Z",
  "principal": {
    "sub": "user123",
    "email": "user@example.com"
  },
  "mcp_request": {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "database_query",
      "arguments": {
        "query": "SELECT * FROM users"
      }
    }
  },
  "context": {
    "server_name": "my-mcp-server",
    "transport": "sse"
  }
}

Response (with JSONPatch):

{
  "version": "v0.1.0",
  "uid": "unique-request-id",
  "allowed": true,
  "patch_type": "json_patch",
  "patch": [
    {
      "op": "add",
      "path": "/mcp_request/params/arguments/audit_user",
      "value": "user@example.com"
    },
    {
      "op": "add",
      "path": "/mcp_request/params/arguments/department",
      "value": "engineering"
    }
  ]
}

JSONPatch Implementation

Use github.com/evanphx/json-patch/v5 library for RFC 6902 compliance.

Supported operations:

  • add - Add value at path
  • remove - Remove value at path
  • replace - Replace value at path
  • copy - Copy value from one path to another
  • move - Move value from one path to another
  • test - Test value at path (for conditional patches)

Security constraint: Patches are scoped to the mcp_request container only. Attempts to modify principal, context, or other fields should be rejected.

// pkg/webhook/mutating/patch.go
func ApplyPatch(original []byte, patch []JSONPatchOp) ([]byte, error)
func ValidatePatch(patch []JSONPatchOp) error
func IsPatchScopedToMCPRequest(patch []JSONPatchOp) bool

Middleware Chain Position

Mutating webhooks should be placed after MCP Parser and before Validating webhooks:

Auth -> Token Exchange -> Tool Filter -> MCP Parser ->
[Mutating Webhooks] -> [Validating Webhooks] -> Telemetry -> Authorization -> Audit -> Recovery

Failure Policies

Policy Behavior on webhook error
fail (fail-closed) Deny request with 500
ignore (fail-open) Use original (unmutated) request

Multiple Webhooks

When multiple mutating webhooks are configured:

  1. Execute in configuration order
  2. Each webhook receives the output of the previous mutation
  3. Patches are applied sequentially
  4. Failure policy applies per-webhook

Tests

  • JSONPatch application tests for all operations
  • Tests for invalid/malformed patches
  • Tests for patch scope validation (reject modifications outside mcp_request)
  • Tests for multiple webhooks with chained mutations
  • Tests for failure policies
  • Integration tests with middleware chain

Acceptance Criteria

  • Middleware registered in GetSupportedMiddlewareFactories()
  • Correctly calls webhooks with RFC-compliant request format
  • Applies JSONPatch operations correctly
  • Validates patch scope (mcp_request only)
  • Implements both failure policies
  • Supports multiple webhooks with chained mutations
  • Comprehensive unit tests with >80% coverage
  • Code passes task lint and task test

Metadata

Metadata

Assignees

Labels

apiItems related to the APIauthenticationenhancementNew feature or requestgoPull requests that update go code

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions