emperor-mcp
The enterprise MCP server framework for Rust — Streamable HTTP first, credential forwarding, audited framing.
Built for teams putting Model Context Protocol servers into production: behind gateways, in front of load balancers, handling other people's credentials, feeding output to models that must not mistake data for instructions.
- Deploy like a service, not a subprocess. Stateless Streamable HTTP: no session
affinity, no
Mcp-Session-Id, every request self-contained — load balancers and failover just work. - Handle credentials without becoming a secret store. Per-request header forwarding through an explicit allow-list; the server forwards and forgets.
- Ship output models can't be hijacked through. Content-aware framing with nonce-guarded delimiters — indirect prompt injection mitigated at the source.
Every opinion is written down, versioned, and mapped to the spec section it narrows:
the Emperor Profile (P1.1). A server built on emperor-mcp is a fully
compliant MCP server — the profile narrows the spec's options, it never breaks them.
Five-minute quickstart
&&
src/main.rs — a complete MCP server with one framed tool:
use ;
use ;
use Arc;
;
async
# from another shell:
# => {"id":1,"jsonrpc":"2.0","result":{"tools":[{"name":"echo", ...}]}}
Any MCP client speaking Streamable HTTP can now call your echo tool — and its output
arrives framed as data, not instructions.
Why "emperor"?
Emperor penguins are the largest of all penguins — and the only ones that breed through the Antarctic winter, the harshest environment on Earth. They don't survive it by improvising. They survive it by protocol: the colony huddles in disciplined formation, every bird rotates through the exposed edge, nobody freelances, and the whole system works precisely because the rules are strict and everyone follows them.
That is exactly the stance this framework takes on running MCP servers in production. The
spec deliberately leaves many choices open — transports, session handling, credential passing.
Openness is right for a spec; in an enterprise deployment, every open choice is a place two
systems disagree at 3 a.m. emperor-mcp closes those choices with one opinionated, fully
spec-compliant profile — and like the huddle, none of it contradicts the wider colony.
The name is also a nod to this crate's origin: it hatched in the kowalski rookery, where the penguins run in hordes. Now it stands on its own ice.
What's inside
- Transport —
McpHandler+ stateless Streamable HTTP + stdio (local/dev) - rmcp bootstrap —
serveat/mcp+/health, graceful shutdown - Output framing —
FrameKind/ prompt-injection mitigation - Credential forwarding —
ForwardedHeadersfor multi-tenant deployments - Host / Origin validation — off behind a validating ingress, in-process with
ServeOptions::with_allowed_hosts/with_allowed_origins - Credential rejection —
credentials_rejected: an upstream401/403becomes the MCP response's HTTP status, so the client can ask the user to sign in again (a clear JSON-RPC error on stdio) - Limits —
Limits: capped characters, results per call and download bytes, with hard ceilings;truncate_charssays what it left out - Lean tool definitions —
schema: one line,Self::tool_router().compact_schemas(), strips JSON Schema boilerplate fromtools/list($schema,nullon optional arguments,"default": null, Rust integer formats, single-use$defs) without losing a description, enum or real bound; 14 to 24% fewer bytes on the servers below - No write tool by default — writing is switched on by the operator (
--writableplus an environment variable,cli::parse_switch); the profile requires it andtestkitchecks it doctor—doctor::Reportand generic checks for each server'sdoctorsubcommand (listen address, env vars, TCP reachability, readable/writable directories, the 8-tool budget)- Optional features —
cli:ServeArgsto#[command(flatten)]into a server's clap parser (--bind,--stdio,--allowed-host,--allowed-origin);testkit:assert_toolsfor a server's own tests (tool count, descriptions, annotations, object input schemas),assert_read_only_default(no write tool without the switch) andassert_compact_schemas; descriptions over 300 characters are a warning
A tool that forwards the caller's credentials and gets refused upstream:
use ;
// inside a #[tool] returning Result<_, rmcp::model::ErrorData>
if let Some = from_status
Two server styles, one set of rules:
// rmcp path: #[tool_router] handlers, credential forwarding wired in
use ;
async
For the hand-rolled McpHandler path, see the quickstart above or
examples/minimal_server.rs. Production consumers:
kowalski-mcp-rookery,
kowalski-mcp-datafusion.
emperor-mcp vs plain rmcp
rmcp is the official Rust MCP SDK, and emperor-mcp is
built on it — this is a layer, not an alternative.
| You need | Reach for |
|---|---|
| Full protocol breadth, custom transports, your own deployment opinions | rmcp directly |
| A production posture out of the box: stateless HTTP, credential forwarding, framed output, a written profile to hold servers to | emperor-mcp |
| A tiny hand-rolled server without SDK machinery | emperor-mcp's McpHandler path |
emperor-mcp pre-makes the deployment decisions and documents them in
PROFILE.md; rmcp stays fully reachable underneath (the serve path takes
any rmcp ServerHandler), so you can drop down whenever you need the SDK's full surface.
Servers built on emperor-mcp
| Server | What it does |
|---|---|
| emperor-folder | One or more folders, confined by cap-std: list, read, search, and with --writable write, edit, move |
| emperor-pdf | Read, search, render, merge/extract and create PDFs; flags hidden text |
| emperor-mail | IMAP/SMTP mailbox: search, read, draft; send only when allowed, to listed recipients |
| emperor-vikunja | Vikunja tasks: list, read, create, update, comment; --read-only |
How they compare with the most popular public MCP servers for the same jobs, and where those are better: docs/COMPARISON.md.
Authoring rules
PROFILE.md— the Emperor Profile (P1.1): the versioned deployment profile, rule by rule, with spec mapping and enforcementMCP_REQUIREMENTS.md— authoring checklist for servers built on this frameworkMANIFEST_SPEC.md— themanifest.yamleach server ships
Build
&&
MSRV: Rust 1.88 (edition 2024), declared via rust-version in Cargo.toml.
Contributing
See CONTRIBUTING.md: run the checks, keep the docs in step, and sign off
your commits (DCO, git commit -s).
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in emperor-mcp by you shall be dual licensed as above, without any additional terms or conditions.