-
Notifications
You must be signed in to change notification settings - Fork 198
Webhook Middleware Phase 3: Mutating webhook middleware with JSONPatch #3398
Description
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 pathremove- Remove value at pathreplace- Replace value at pathcopy- Copy value from one path to anothermove- Move value from one path to anothertest- 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) boolMiddleware 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:
- Execute in configuration order
- Each webhook receives the output of the previous mutation
- Patches are applied sequentially
- 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 lintandtask test