Plugin Configuration
Overview
The mcpd daemon supports a plugin subsystem for extending request/response processing.
This page covers configuring plugins. To build one, see Writing a Plugin.
Plugin Categories
Plugins are organized into categories and execute during specific phases of the request lifecycle.
Categories execute in the order shown below for both request and response phases.
1
observability
Collect metrics and traces (non-blocking)
Parallel
2
authentication
Validate client identity
Sequential
3
authorization
Verify permissions after authentication
Sequential
4
rate_limiting
Enforce request rate limits
Sequential
5
validation
Check request/response structure and content
Sequential
6
content
Transform request/response payloads
Sequential
7
audit
Log compliance and security events
Sequential
Plugin Execution Flows
Plugins can execute during one or both flows/phases:
request: Executes during the request phaseresponse: Executes during the response phase
Configuration Format
Plugin Directory
Plugins are native executables. [plugins].dir is the directory mcpd scans to find them, and every plugin's name must match a binary in that directory.
dir
string
Yes, if any plugins
Directory containing the plugin binaries
When scanning the directory, mcpd only considers regular files with the execute bit set. Subdirectories, dotfiles, and non-executable files are skipped silently, so a plugin whose binary lost its execute permission looks identical to one that was never there.
Both problems are caught at startup rather than at request time:
If you see the second error and the file is definitely there, check ls -l for the execute bit before anything else.
Plugin Fields
name
string
Yes
Name of the plugin binary in the plugins directory
commit_hash
string
No
SHA/hash for validating plugin version
required
boolean
No
Whether plugin failure should block the request
flows
array
Yes
Execution phases: ["request"], ["response"], or both
Execution Order
Plugins execute in the order they appear in the configuration file within their category.
During the request phase, jwt-auth executes first, followed by api-key-auth.
Required Plugins
Required Plugin Failures
If a required plugin fails, the request is rejected with HTTP 500 (Internal Server Error) and a Mcpd-Error-Type header indicating the failure phase.
Mark plugins as required when their successful execution is critical:
Failure Behavior
When a required plugin fails, mcpd returns:
Status: 500 Internal Server Error
Header:
Mcpd-Error-Typewith one of:request-pipeline-failure- Plugin failed during request processing (before upstream call)response-pipeline-failure- Plugin failed during response processing (after upstream call)
Optional Plugin Behavior
When required is not specified or set to false:
Plugin errors (crashes, exceptions): Logged as warnings, pipeline continues.
Plugin rejections (returning
Continue=false): Pipeline respects the rejection and stops processing, except:Observability category only: Pipeline ignores optional plugin rejections and continues (necessary for parallel execution model).
Content Mutation
Content plugins modify the request by setting the modified request in their response. Other plugin categories can only observe or reject requests.
Example Content Plugin Flow
The encryption plugin processes the request first and may modify it. The modified request is then passed to the compression plugin.
Observability Plugin Execution
Observability plugins are designed for metrics collection, tracing, and monitoring. They execute concurrently for performance.
Required Observability Plugins
If any observability plugin is marked as required, request processing waits for all observability plugins to complete before aggregating results. If any required observability plugin fails, the request is rejected after all have completed.
In this example, both metrics and tracing run in parallel, but the request will be rejected if metrics fails (once metrics and tracing have completed).
Multiple Plugins Per Category
You can configure multiple plugins within the same category. They execute in the order defined:
Request processing order: jwt-auth → api-key-auth → oauth2
Minimal Configuration
Plugins are optional. A configuration file without plugins is valid:
Complete Example
Execution Flow
Request Phase
jwt-auth(authentication) - sequentialrbac(authorization) - sequentialtoken-bucket(rate_limiting) - sequentialschema-validator(validation) - sequentialencryption(content) - sequentialprometheus-metrics+distributed-tracing(observability) - parallel
Response Phase
schema-validator(validation) - sequentialencryption(content) - sequentialprometheus-metrics+distributed-tracing(observability) - parallelcompliance-logger(audit) - sequential
Running Plugins in Docker
A containerized mcpd cannot see plugin binaries on the host unless the directory is bind mounted. Mount it and point [plugins].dir at the path inside the container:
The image runs as the non-root mcpd user, so the mounted binaries must be readable and executable by that user, not only by whoever owns them on the host. The mount is shown read-only because mcpd only needs to execute the plugins, never write to them.
Architecture and libc compatibility
Plugins are separate executables that mcpd launches and talks to over gRPC, so each one must be built for the OS, architecture, and C library of whatever runs mcpd — which is the container image, not your laptop, whenever mcpd itself is containerized.
The published image is multi-arch — mzdotai/mcpd:v0.4.0 ships both linux/amd64 and linux/arm64 — so Docker pulls the variant matching your host, and the plugin must be built for that architecture. It is also Alpine-based (musl), so a plugin dynamically linked against glibc will not run even at the correct architecture. For Go plugins, build a static binary for the architecture Docker pulled:
Run docker image inspect mzdotai/mcpd:v0.4.0 --format '{{.Architecture}}' if you are unsure which one you have, or if you pinned a platform with --platform. CGO_ENABLED=0 produces a static binary and sidesteps the glibc/musl question entirely; if you need cgo, build against musl.
The failure is easy to misread, because discovery succeeds and only the launch fails:
exec format error
Wrong architecture or OS for the running platform
no such file or directory, but the binary exists
Dynamically linked against a libc the image lacks (typically glibc on musl)
permission denied
Execute bit set, but not executable by the mcpd user (wrong owner/group), or unreadable
A binary with no execute bit at all never reaches this stage — it is skipped during discovery and produces the "plugin … not found in directory" startup error described above instead.
The second row is genuinely confusing: the kernel reports the missing interpreter, not the missing binary. Check with file plugins/jwt-auth — it is portable and reports "statically linked" vs "dynamically linked". (ldd is Linux-only, so it is unavailable on the macOS host you may be cross-compiling from, and glibc's ldd prints "not a dynamic executable" for a correct CGO_ENABLED=0 binary.)
Validation
Plugin configurations are validated when the daemon starts or during hot reload. Common validation errors:
Empty plugin name
Missing or empty
flowsarrayInvalid flow values (must be
requestorresponse)Duplicate flow values
Plugin directory missing, or configured plugin not found in it (see Plugin Directory)
For runtime plugin failures or binary checks, see Troubleshooting.
Last updated