rmcp-server-kit
rmcp-server-kit is a production-grade, reusable framework for building Model Context Protocol servers in Rust. It provides a Streamable HTTP transport with TLS/mTLS, structured observability, authentication (Bearer / mTLS / OAuth 2.1 JWT), role-based access control (RBAC), per-IP rate limiting, and optional Prometheus metrics -- all wired up and ready to go.
You supply a rmcp::handler::server::ServerHandler implementation; rmcp-server-kit
handles everything else.
Quick Start
[]
= "3"
= { = "3", = ["server", "macros"] }
= { = "1", = ["rt-multi-thread", "macros", "signal"] }
The minimal example below uses default features only. Enable the
oauthfeature (features = ["oauth"]) to validate JWTs against a JWKS, ormetricsfor the Prometheus/metricsendpoint -- see the Cargo features table below.
use ;
use ServerHandler;
use ;
;
async
Full API documentation and worked examples live in
docs/GUIDE.md. For loading configuration from a TOML file and
bridging it into McpServerConfig via ServerConfig::apply_to_mcp_config, see
the TOML configuration reference.
Runnable end-to-end examples ship in the repository:
Common configurations
API key + RBAC + per-tool argument allowlist:
use ;
use ;
use Arc;
let = generate_api_key?;
let auth = with_keys;
let viewer = new
.with_argument_allowlists;
let rbac = new;
Use
ArgumentAllowlist::new_required(as above), notnew, unless omitting the argument is genuinely safe.requireddefaults tofalse-- permanently -- so an allowlist built withnewconstrains the value only when the caller supplies it, and a tool that substitutes its own default for a missing argument bypasses the allowlist entirely. See the argument allowlist guide.
OAuth 2.1 resource server (JWT validation against JWKS):
use AuthConfig;
use OAuthConfig;
let oauth = builder
.scope
.scope
.build;
let mut auth = with_keys;
auth.oauth = Some;
The OAuth fetcher and the shared
OauthHttpClientenforce a strict per-hop SSRF guard and a fail-closed cap on JWKS key counts. Construct the client viaOauthHttpClient::with_config(&oauth_config)so the configured CA bundle, the SSRF guard, and the HTTPS-downgrade-rejecting redirect policy are all wired in one call. SeeSECURITY.mdfor the trust model.
OAuth in-cluster IdP (private/loopback IdP target, opt-in):
use ;
// `OAuthSsrfAllowlist` is `#[non_exhaustive]`; build it via
// `Default::default()` and push into the public fields.
let mut allowlist = default;
allowlist.hosts.push;
allowlist.cidrs.push;
let oauth = builder
.ssrf_allowlist
.build;
The default fail-closed SSRF guard blocks targets that resolve into private (RFC 1918), loopback, CGNAT, or unique-local space. Use
ssrf_allowlistonly when the IdP legitimately lives there (e.g. a KeycloakServiceClusterIP). Cloud-metadata addresses (AWS / GCP / Alibaba) remain unbypassable regardless of the allowlist contents. Seedocs/GUIDE.mdand the "Operator allowlist" subsection ofSECURITY.mdfor the full trust model.
Prometheus metrics on a separate listener:
let config = new
.with_metrics;
TLS:
let config = new
.with_tls;
Features
- Transport: Streamable HTTP (
/mcp), health (/healthz,/readyz), admin diagnostics, graceful shutdown, configurable TLS and mTLS. - Auth: API-key (Argon2 hashed), mTLS client certs, OAuth 2.1 JWT validation against JWKS (feature-gated).
- RBAC: Tool-scoped allow-lists with per-role argument constraints and
task-local
current_role()/current_identity()accessors. - Observability: Tracing, JSON logs, optional audit-file sink.
- Hardening: Per-IP rate limiting (governor), request-body caps, OWASP security headers, configurable CORS and Host allow-lists.
- Metrics: Prometheus
/metricsendpoint (opt-in viametricsfeature).
Cargo features
| Feature | Default | Description |
|---|---|---|
oauth |
No | OAuth 2.1 JWT validation via JWKS. |
oauth-mtls-client |
No | RFC 8705 mTLS client authentication for the OAuth token-exchange endpoint. Implies oauth. |
metrics |
No | Prometheus metrics registry and /metrics. |
test-helpers |
No | Test-only helpers for downstream integration tests. Never enable in a production build -- see the warning below. |
test-helpersis not safe in production. It is not part of the stable API surface and carries no semver guarantees across minor releases. Some of the helpers it exposes deliberately bypass SSRF screening, the JWKS refresh cooldown, the CDP discovery rate limiter, and CRL verifier publication. Enable it only in test and integration builds. See the Cargo features section of the guide for the per-helper detail.
Design decisions
Status: NO-GO. Reviewed and decided; recorded here so the reasoning is not rediscovered. The crate remains fully RFC-conformant without it.
What is missing
RFC 8693 defines two exchange semantics:
| Semantics | Meaning | Parameters |
|---|---|---|
| Impersonation (implemented) | The server acts as the user. Downstream sees only the user. | subject_token |
| Delegation (not implemented) | The server acts on behalf of the user while remaining visible. Downstream sees both parties. | subject_token + actor_token |
Delegation produces an act claim chain, letting a downstream service record
"service X acted for user Y" rather than just "user Y did this". In practice
this crate can only say the latter.
Per RFC 8693 §2.1, actor_token is OPTIONAL, and actor_token_type is
"REQUIRED when the actor_token parameter is present in the request but MUST
NOT be included otherwise." Because both are optional, omitting them is
conformant. This is a missing capability, not a defect.
Why NO-GO
- Already conformant. Nothing is broken; no spec violation exists.
- No user demand. Identified during an internal review, not requested.
- Thin real-world support. Keycloak's delegation support is limited, and Microsoft Entra ID does not use RFC 8693 for its on-behalf-of flow at all.
- The blocker is credential acquisition, not serialization. Delegation needs a token representing the server's own identity. This crate has no client-credentials flow and no way to obtain one. Adding it means a second OAuth client inside the crate - token cache, refresh scheduling, failure policy, SSRF/TLS handling - which is far larger than adding two form parameters.
Revisit criteria
Reopen when all of these are known:
- A named consumer requires delegation, with a concrete audit/compliance use case
- A named authorization server in their stack that actually supports RFC 8693 delegation
- A chosen actor-token acquisition model (see below)
- An expiry/rotation strategy for that credential
If revisited - design notes
Acquisition model. Preferred: an application-supplied async callback -
the application already owns service-identity lifecycle. Explicitly rejected:
reusing the mTLS client_cert identity, which is RFC 8705 §2 client
authentication, not an RFC 8693 actor token.
Known defect in the first sketch. build_exchange_form is synchronous,
so an async token provider cannot be invoked from it. The actor token must be
resolved before form construction, higher in the exchange path. Any future
attempt hits this immediately.
Serde feasibility (verified). TokenExchangeConfig derives
Debug, Clone, Deserialize with #[serde(deny_unknown_fields)] and is
#[non_exhaustive]. A #[serde(skip)] provider field composes correctly and
does not break existing TOML parsing. Clone survives with
Option<Arc<dyn _>>; Debug only survives if the trait itself requires
Debug - note ToolHooks sidesteps this by not deriving Debug.
Mandatory security constraints:
- Store as
secrecy::SecretString; never logged, debug-printed, audited, or included in errors - Least-privilege scope/audience - this is the service identity, so a leak affects every delegated exchange, not one user
- Do not reuse
client_secretas actor proof; client authentication and actor identity are distinct credentials - Stale or expired actor tokens must fail closed
- Preserve the RFC 8693 §2.1 invariant: emit
actor_token_typeiffactor_tokenis present - Preserve byte-identical request output for any config that does not opt into delegation
Minimum supported Rust
rmcp-server-kit targets stable Rust 1.98 or newer (tracks edition = "2024").
Repository
- GitHub (canonical): https://github.com/andrico21/rmcp-server-kit
The canonical release artifact is the rmcp-server-kit crate on crates.io.
License
Dual-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.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.