MCPG plugins are how the gateway gets new capabilities — identity backends, policy engines, transforms, bindings, observability sinks. Two loaders ship: native Rust cdylibs (max performance, full async-trait) and WASM Component Model (sandboxed, language-agnostic).
This guide walks through writing a transform plugin in native Rust — small enough to fit in one page, useful enough to teach the SDK.
What's a transform plugin?
A transform plugin runs before dispatch (mutating the request) and/or after dispatch (mutating the response). Common use cases:
- Mask PII fields (
transforms.masking) - Inject correlation headers
- Normalize timestamps to UTC
- Strip unsupported fields for older upstream tools
1. Set up the crate
# Cargo.toml
[package]
name = "mcpg-plugin-redact-emails"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
mcpg-plugin-sdk = "1.0"
async-trait = "0.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
regex = "1"
2. Implement the trait
// src/lib.rs
use async_trait::async_trait;
use mcpg_plugin_sdk::{
register_plugin, transform::*, Capability, PluginInfo, ToolRequest, ToolResponse,
};
use regex::Regex;
struct RedactEmails {
pattern: Regex,
}
#[async_trait]
impl TransformPlugin for RedactEmails {
fn info(&self) -> PluginInfo {
PluginInfo {
id: "redact-emails".into(),
version: "0.1.0".into(),
capabilities: vec![],
}
}
async fn before_dispatch(&self, _req: &mut ToolRequest) -> Result<(), Error> {
Ok(())
}
async fn after_dispatch(&self, resp: &mut ToolResponse) -> Result<(), Error> {
if let Some(text) = resp.text_mut() {
*text = self.pattern.replace_all(text, "[redacted]").to_string();
}
Ok(())
}
}
register_plugin!(RedactEmails {
pattern: Regex::new(r"[\w\.-]+@[\w\.-]+\.[a-z]+").unwrap(),
});
3. Build
cargo build --release
# Output: target/release/libmcpg_plugin_redact_emails.so (Linux)
# target/release/libmcpg_plugin_redact_emails.dylib (macOS)
4. Sign
Plugin artifacts must be Ed25519-signed before they can be loaded by a gateway with
require_signed_plugins: true (default in production).
mcpg-plugins sign \
--key path/to/signing-key.pem \
target/release/libmcpg_plugin_redact_emails.so
This produces a sidecar .sig file. The gateway verifies on load against the public
key configured in the gateway's plugin policy.
5. Pack
Bundle the artifact, sidecar signature, and plugin descriptor into a single archive:
mcpg-plugins pack \
--descriptor plugin.json \
--artifact target/release/libmcpg_plugin_redact_emails.so \
--signature target/release/libmcpg_plugin_redact_emails.so.sig \
--out redact-emails-0.1.0.zip
The descriptor:
{
"id": "redact-emails",
"version": "0.1.0",
"kind": "transform",
"required_capabilities": [],
"tags": ["security", "privacy"]
}
6. Push to OCI
mcpg-plugins push \
--tag oci.example.com/mcpg-plugins/redact-emails:0.1.0 \
redact-emails-0.1.0.zip
OCI registries that work: ghcr.io, ECR, GCR, Harbor, Zot, Quay.
7. Use it
In your gateway config:
plugins:
- id: redact-emails
source:
oci: oci.example.com/mcpg-plugins/redact-emails:0.1.0
public_key: |
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----
bindings:
- id: support-tool
transforms:
- redact-emails
The gateway pulls, verifies, loads, and runs.
WASM Component plugins
WASM Components compile from any language with a Component Model toolchain (Rust, Go, JavaScript, Python via componentize-py). The protocol is the same; only the ABI differs:
// Same transform, but as a WASM Component
wit_bindgen::generate!("redact-emails");
struct Component;
impl Guest for Component {
fn after_dispatch(resp: ToolResponse) -> Result<ToolResponse, String> {
// ...
}
}
Build with cargo component build --release --target wasm32-wasip2.
The trade-off: WASM gives sandboxing, faster cold-start, and language-agnosticism. Native Rust gives ~10× more throughput on hot paths. Use WASM when you want untrusted plugins or polyglot teams; native when you control the supply chain and need performance.
Capabilities
Plugins declare what host capabilities they need (cap.host.outbound_http,
cap.host.secret_store, etc.). The gateway refuses to start if a plugin requires a
capability the operator hasn't explicitly granted. See the
security model article for the full capability set.
What's next
- Plugin architecture — how the loader, ABI, and signing chain fit together
- The OSS catalog: 38 plugins to crib from. All Apache-2.0.