envoy_mcp_openapi_processor

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 42 Imported by: 0

README

envoy-mcp-openapi-processor

An Envoy external processor that transforms Model Context Protocol (MCP) requests into upstream HTTP API calls based on OpenAPI specifications.

The external processor server communicates only with Envoy over gRPC via a Unix domain socket. Envoy owns all downstream client and upstream service connections, while the processor inspects and can mutate request and response data relayed by Envoy.

Installation

go get github.com/ing-bank/envoy-mcp-openapi-processor

Quick Start

See examples/mcp-server for a complete working example including Envoy proxy configured to use the envoy-mcp-openapi-processor server.

Supported MCP Protocol Versions

The processor is a dual-era MCP server supporting 2025-06-18, 2025-11-25 and 2026-07-28. The two eras have disjoint entry points, so every request selects one unambiguously:

  • Legacy era (2025-06-18, 2025-11-25): the client starts with the initialize handshake and declares no version on subsequent requests. Version negotiation never yields a modern version, so a client that asks for one over the handshake is answered with a version it can actually use statefully.
  • Modern era (2026-07-28): there is no handshake. The mandatory server/discover RPC replaces initialize, and every request carries its protocol version in params._meta (io.modelcontextprotocol/protocolVersion) and mirrors it in the MCP-Protocol-Version header, alongside Mcp-Method (and Mcp-Name for tools/call). Those headers are validated against the JSON-RPC body and mismatches are refused with HTTP 400 and JSON-RPC error -32020. Results carry resultType and the serverInfo _meta entry, and an unknown method answers HTTP 404 rather than the legacy 200.
Result caching

Modern server/discover and tools/list results carry fixed cache hints: ttlMs: 300000 (five minutes) and cacheScope: "private". The tool list changes only on redeploy, and an ext_proc filter cannot push notifications/tools/list_changed, so the TTL is what brings a client back. Five minutes bounds how long a client may keep using a retired list. Re-fetching is cheap, since both results are served from memory and never reach the upstream API.

OpenTelemetry

To enable export of logs and traces to an OpenTelemetry collector, use one of the options below. If neither is used, the server runs with a no-op tracer provider and a no-op logger.

Option 1: Use provided initialization functions
package main

import (
	"context"

	mcp_proc "github.com/ing-bank/envoy-mcp-openapi-processor"
)

func main() {
	telemetryConfig := mcp_proc.TelemetryConfig{
		OtelEndpoint:   "otel-collector:4317",
		ServiceName:    "mcp-sidecar",
		ServiceVersion: "1.0.0",
	}
	ctx := context.Background()
	err := mcp_proc.InitLogger(telemetryConfig)
	// ...
	tracerShutdown, err := mcp_proc.InitTracer(ctx, telemetryConfig)
	// ...
	var cfg mcp_proc.Config
	// ...
	mcp_proc.RunServer(ctx, &cfg)
}
Option 2: Bring your own provider

Set the global tracer provider by calling otel.SetTracerProvider(myTracerProvider) and the global zap logger by calling zap.ReplaceGlobals(myLogger) before starting the server.

Log verbosity

TelemetryConfig.LogLevel sets the minimum severity written to the console and exported to OpenTelemetry. It accepts "debug", "info", "warn" or "error"; empty/unset means "info".

If you build the logger yourself instead, pass the level to CreateConsoleCore(level) as a zapcore.Level.

Development

Tests

To run all tests, execute the following command:

make test

To check the test coverage, execute the following command:

make coverage-check
E2E tests

To run the end-to-end suite (spins up Envoy, the processor, and a mock upstream via Docker Compose, then exercises MCP <=> REST translation), execute the following command:

make e2e

Requires Docker. On failure, container logs are captured in e2e/last-run.log.

Fuzz tests

Run server handler fuzz tests one at a time:

make fuzz-request
make fuzz-response

Each command runs until stopped with Ctrl+C and reports any issues found.

DNS Rebinding Protection

The processor validates the Host/:authority and Origin headers of every request. By default only loopback hostnames are accepted: localhost, 127.0.0.1 and ::1 (any port); anything else is refused with HTTP 403. This protects unauthenticated localhost deployments against DNS rebinding attacks where a malicious website's DNS resolves to 127.0.0.1 so the victim's browser sends same-origin requests to the local server.

A missing Host header is rejected (fail closed). A missing Origin header is accepted (non-browser clients do not send one), but a present Origin must be a URL whose hostname is allowed.

Deployments serving other domains configure them via Config.AllowedHosts:

cfg := &mcp_proc.Config{
    // ...
    AllowedHosts: []string{"mcp.example.com"},
}

Matching is case-insensitive and ignores ports. A non-empty list replaces the localhost default — include "localhost" explicitly if it should remain allowed.

Each entry is one of:

  • "*" — match any host. This disables DNS rebinding protection, so use it only on trusted networks.
  • "*.example.com" — match any subdomain at any depth (foo.example.com, a.b.example.com) but not the apex example.com.
  • "mcp.example.com" — exact match.

Security Checklist

Before deploying to production, we advise to verify if the following controls are in place

Container Security
  • Container runs as non-root user
  • Read-only root filesystem where possible
  • No privileged mode
  • Resource limits (CPU, memory) configured
  • seccomp profile applied
  • AppArmor/SELinux enabled
  • Container image scanned for CVEs
  • Image signed and verified
Envoy Configuration
  • TLS configured for downstream connections
  • TLS configured for upstream connections
  • Buffer limits set (≤32KB for edge deployments)
  • Connection limits configured
  • Stream limits configured (≤100 concurrent for HTTP/2)
  • Timeouts configured (connection, stream, request)
  • Overload manager enabled
  • Admin endpoint restricted to localhost
  • use_remote_address: true for edge deployments
  • Path normalization enabled
  • headers_with_underscores_action: REJECT_REQUEST

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CreateConsoleCore

func CreateConsoleCore(level zapcore.Level) zapcore.Core

CreateConsoleCore returns a zapcore.Core that writes logs at or above level to stdout and error logs to stderr using a console encoder.

func CreateNewLoggerFromCore

func CreateNewLoggerFromCore(core zapcore.Core) *zap.Logger

CreateNewLoggerFromCore creates a named zap.Logger from the given core with caller information enabled.

func InitLogger

func InitLogger(config TelemetryConfig) error

InitLogger sets up the global logger to use the OTel bridge, allowing logs to be exported to OTel. Logs below config.LogLevel are written neither to the console nor to OTel.

func InitTracer

func InitTracer(ctx context.Context, config TelemetryConfig) (func(context.Context) error, error)

InitTracer initializes the OpenTelemetry tracer and returns a shutdown function

func RunServer

func RunServer(ctx context.Context, cfg *Config) error

RunServer starts the ext_proc gRPC server on a Unix domain socket, loading MCP tools from the OpenAPI specs specified in cfg and serving Envoy external processing requests until ctx is cancelled.

Types

type Config

type Config struct {
	// SocketPath is the Unix domain socket path the gRPC server listens on.
	SocketPath string
	// ToolRegistryConfig configures how tools are loaded from OpenAPI specs.
	ToolRegistryConfig *ToolRegistryConfig
	// ServerInfo configures the identity and instructions for the MCP server.
	ServerInfo ServerInfo
	// AllowedHosts is the list of hostnames accepted in the Host/:authority and
	// Origin headers of incoming requests (DNS rebinding protection). Matching
	// is case-insensitive and ignores ports. Each entry is one of:
	//   - "*" — match any host (disables rebinding protection; opt-in only);
	//   - "*.example.org" — match any subdomain at any depth (foo.example.org,
	//     a.b.example.org) but NOT the apex example.org;
	//   - "host.example.org" — exact match.
	// Empty means the default policy: localhost, 127.0.0.1 and ::1. A non-empty
	// list REPLACES the default — include "localhost" explicitly if it should
	// remain allowed.
	AllowedHosts []string
}

Config holds the configuration for the ext_proc gRPC server.

type ServerInfo

type ServerInfo struct {
	// Name is the server name reported to MCP clients.
	Name string
	// Version is the server version reported to MCP clients.
	Version string
	// Instructions is human-readable text returned to MCP clients.
	Instructions string
}

ServerInfo holds the identity and instructions returned in the MCP initialize response.

type TelemetryConfig

type TelemetryConfig struct {
	// OtelEndpoint is the gRPC address of the OpenTelemetry Collector (e.g. "localhost:4317").
	OtelEndpoint string
	// ServiceName identifies this service in traces and logs.
	ServiceName string
	// ServiceVersion is the version string reported in telemetry resource attributes.
	ServiceVersion string
	// LogLevel is the minimum severity written to the console and exported to
	// OTel, one of "debug", "info", "warn" or "error". Empty means "info".
	LogLevel string
}

TelemetryConfig holds the configuration for OpenTelemetry tracing and logging exporters.

type ToolRegistryConfig

type ToolRegistryConfig struct {
	// OpenAPISpecPattern is a glob pattern to match OpenAPI spec files for loading tools.
	// For example, "specs/*.yaml" would load all YAML files in the specs directory.
	OpenAPISpecPattern string
	// ToolAllowlist is an optional list of tool names to allow. If empty, all tools loaded from the OpenAPI specs will be allowed.
	ToolAllowlist []string
	// StructuredOutput indicates whether to enable structured output for tools,
	// which allows using outputSchema for response validation and structured content in tool responses.
	StructuredOutput bool
}

ToolRegistryConfig defines the configuration for creating a toolRegistry from OpenAPI specs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL