A config-driven MCP server core built on the official Rust SDK (rmcp). Define tools, auth, prompts, resources, and HTTP behavior in YAML or JSON configuration -- the library handles execution, validation, and protocol compliance with minimal Rust code.
Fully implements the Model Context Protocol specification (2025-11-25).
This library has been tested and verified with:
- Claude Code
- Codex CLI
- Gemini CLI
- Reduced boilerplate. Stand up a spec-compliant MCP server by writing configuration instead of protocol plumbing. A working server with HTTP tools needs only a YAML file and a few lines of Rust.
- Built-in HTTP API tooling. Define outbound HTTP tool calls entirely in config with URL templating, header injection, query parameters, and structured response mapping.
- Authentication out of the box. Supports inbound bearer token, JWT/JWKS validation, and OAuth token introspection with scope enforcement and
WWW-Authenticatechallenges. Also supports outbound upstream auth for HTTP tools (none,bearer,basic,oauth2) including OAuth2 client-credentials/refresh-token grants with optional mTLS at the token endpoint. - Extensible plugin system. When config-driven behavior is not enough, register plugins for custom tool logic, auth validation, prompt/resource providers, completion providers, and HTTP router extensions.
- Both transports. Works with stdio and streamable HTTP transports.
- Works with the official SDK. This library builds on rmcp and uses its transport runtime,
ServerHandlertrait, and MCP type definitions directly -- no reimplementation.
- Not a toolbox. This library does not include built-in tools like file servers, database connectors, or shell executors. The only built-in execution type is outbound HTTP calls. Any other tool behavior must be provided through a tool plugin.
The rmcp SDK provides low-level MCP protocol primitives: transport, message framing, the ServerHandler trait, and type definitions. rust-mcp-core builds on top of that to provide a config-driven server framework.
What the SDK provides (used directly, not reimplemented):
- Transport runtime (stdio + streamable HTTP)
ServerHandlertrait and JSON-RPC dispatch- All MCP model types (
Tool,Prompt,Resource,Task, etc.) - Default handler implementations (e.g.,
ping)
What rust-mcp-core adds:
- Config loading with
${env:ENV}expansion and JSON schema validation - Tool execution engine (HTTP tools with templating + plugin tools)
- Output schema validation and structured content rendering
- Plugin registries (tool, auth, prompt, resource, completion, HTTP router)
- Config-driven prompts, resources, and completion providers
- Auth middleware (bearer, JWT/JWKS, introspection, scope enforcement)
- Task store with peer isolation, TTL, cooperative cancellation, and status notifications
The complementary relationship is clearest with auth. The SDK provides client-side OAuth (PKCE flows, token acquisition, credential storage, automatic refresh). rust-mcp-core provides server-side auth (token validation, scope enforcement, WWW-Authenticate challenges, and the protected resource metadata endpoint). An rmcp client obtains a token and sends it; a rust-mcp-core server receives and validates it.
This library implements the following capabilities from the MCP 2025-11-25 specification:
| Capability | Description |
|---|---|
| Tools | Config-driven and plugin-driven tool definitions. Supports input/output schema validation, list-changed notifications, and pagination. |
| Prompts | Inline (config-driven) and plugin-driven prompt providers with argument validation, template rendering, and list-changed notifications. |
| Resources | Inline and plugin-driven resource providers with subscribe/unsubscribe support and list-changed notifications. |
| Completion | Autocompletion for prompt and resource template arguments from inline value lists or plugin sources. |
| Logging | Structured log messages via notifications/message with syslog severity levels. Clients control notification threshold via logging/setLevel. |
| Progress | Long-running operation tracking via notifications/progress with rate limiting and monotonic progress enforcement. |
| Cancellation | In-progress request termination. Cancellable tools are aborted automatically; non-cancellable tools receive a fresh token and run to completion. |
| Tasks | Experimental task utility for long-running operations. Supports task-augmented tool calls, polling, deferred result retrieval, cooperative cancellation, TTL, peer isolation, and status notifications. |
| Pagination | Cursor-based pagination for tools/list and other list operations. |
These require a registered plugin to invoke. The framework handles config validation and client capability negotiation; plugin code calls helpers via params.ctx (params: PluginCallParams).
| Feature | Description |
|---|---|
| Sampling | Request LLM text/image/audio generation from the client, optionally with tool use. |
| Roots | Query the client for filesystem root boundaries. |
| Elicitation | Request structured user input (form mode) or trigger out-of-band interactions like OAuth flows (URL mode). |
- Capability negotiation during
initializehandshake - Ping/pong for connection health
- Feature-gate enforcement: disabled features return
method-not-foundand are omitted from capability advertisement
All features are enabled by default. Disable with default-features = false and enable selectively.
| Feature | Description | Implies |
|---|---|---|
streamable_http |
HTTP transport (server.transport.mode=streamable_http) and HTTP router plugin surface |
-- |
http_hardening |
Streamable HTTP hardening middleware (max_request_bytes, inbound rate limits, session abuse controls, panic/sensitive-header guards) |
streamable_http |
auth |
Auth middleware: bearer, JWT/JWKS, OAuth introspection, scope enforcement | streamable_http |
http_tools |
Built-in outbound HTTP tool execution (tools.items[].execute.type=http) plus upstream auth (none, bearer, basic, oauth2) |
-- |
prompts |
prompts/list + prompts/get capability |
-- |
resources |
resources/list, resources/read, resources/templates/list, subscribe/unsubscribe |
-- |
completion |
completion/complete for prompt/resource argument autocompletion |
-- |
client_logging |
logging/setLevel + notifications/message |
-- |
progress_utility |
notifications/progress via params.ctx.notify_progress(...) |
-- |
tasks_utility |
Experimental: task-augmented tools/call, tasks/get, tasks/list, tasks/cancel |
-- |
client_features |
Server-initiated client helpers via params.ctx: request_roots(), request_sampling(), request_elicitation() |
-- |
Minimal build (stdio + plugin tools only):
cargo build --no-default-featuresIf config references a disabled feature, startup fails with a clear error message.
From crates.io:
[dependencies]
rust-mcp-core = "0.1"From GitHub:
[dependencies]
rust-mcp-core = { git = "https://github.com/nullablevariant/rust-mcp-core" }Create a YAML config file. This example defines one HTTP tool and one inline prompt over streamable HTTP transport. Stdio transport is also supported by setting transport.mode: stdio.
If you want a full copy/paste starter with every supported field, use mcp_config.template.yml.
version: 1
server:
host: 0.0.0.0
port: 3000
endpoint_path: /mcp
logging:
level: info
transport:
mode: streamable_http # also supports: stdio
auth:
enabled: false
client_logging:
level: info
upstreams:
api:
base_url: ${env:API_BASE_URL}
tools:
items:
- name: api.list_items
description: List items from the API
input_schema:
type: object
properties:
query:
type: string
execute:
type: http
upstream: api
method: GET
path: /items
query:
q: "${query}"
response:
type: structured
template:
items: "${$.items}"
fallback: textuse std::path::PathBuf;
use rust_mcp_core::{load_mcp_config_from_path, runtime, PluginRegistry};
use rust_mcp_core::McpError;
#[tokio::main]
async fn main() -> Result<(), McpError> {
let config = load_mcp_config_from_path(PathBuf::from("config/mcp_config.yml"))?;
let plugins = PluginRegistry::new();
runtime::run_from_config(config, plugins).await
}With a custom tool plugin:
use rust_mcp_core::{load_mcp_config_from_path, runtime, McpError, PluginRegistry};
#[tokio::main]
async fn main() -> Result<(), McpError> {
let config = load_mcp_config_from_path("config/mcp_config.yml".into())?;
let plugins = PluginRegistry::new()
.register_tool(MyToolPlugin)?;
runtime::run_from_config(config, plugins).await
}Any plugin referenced by config (for example via tools.items[].execute.plugin or provider plugin fields) must be both declared in config plugins[] and registered in PluginRegistry. Extra registered plugins that are not declared are ignored with a warning. See Plugin Guide for the full plugin contract.
runtime::run_from_config(...) is a convenience entrypoint and does not expose reload control.
If you need config reload:
- use
runtime::build_runtime(...), - keep the returned
runtimehandle, - load updated config input yourself,
- call
runtime.reload_config(new_config).await.
rust-mcp-core does not automatically watch config files or trigger reloads.
use std::path::PathBuf;
use rust_mcp_core::{load_mcp_config_from_path, runtime, McpError, PluginRegistry};
#[tokio::main]
async fn main() -> Result<(), McpError> {
let initial = load_mcp_config_from_path(PathBuf::from("config/mcp_config.yml"))?;
let runtime = runtime::build_runtime(initial, PluginRegistry::new()).await?;
// Consumer-owned trigger (file watcher, signal, admin endpoint, etc.)
let updated = load_mcp_config_from_path(PathBuf::from("config/mcp_config.reload.yml"))?;
runtime.reload_config(updated).await?;
runtime.run().await
}upstreams.<name>.auth controls outbound auth for tools.items[].execute.type=http:
type: none-> no auth header injected.type: bearer-> injectsAuthorization: Bearer <token>.type: basic-> injects HTTP Basic auth.type: oauth2-> acquires/caches access tokens viaclient_credentialsorrefresh_tokengrant, injects bearer token, and can retry once on401after forced refresh.
Token-endpoint secrets support inline, env, and path sources. Optional token-endpoint mTLS is configured via auth.mtls (client_cert, client_key, optional ca_cert). See Auth and Config Schema for full field-level details.
When server.transport.mode=streamable_http, manual clients (for example curl) should follow this flow:
- Send
initializefirst, captureMCP-Session-Idfrom response headers when sessions are enabled. - Send
notifications/initializedon the same endpoint with the same session header. - Include
Accept: application/json, text/event-streamon POST requests. - If a client sends
MCP-Protocol-Versionon HTTP requests:- default
server.transport.streamable_http.protocol_version_negotiation.mode: strictkeeps the header; unsupported values return HTTP400. mode: negotiatekeeps known RMCP versions and drops unknown versions before rmcp validation.
- default
- Reuse the
MCP-Session-Idheader on subsequent requests in session modes that require it. - Expect streamable HTTP responses to be SSE-framed.
Why this exists:
rust-mcp-coreis coupled to RMCP's known protocol version set.- Some AI clients send a newer date-stamped
MCP-Protocol-Versionheader than the bundled RMCP supports. - In
strictmode this fails fast with400(explicit mismatch). - In
negotiatemode unknown header values are removed so requests can continue without forcing a reverse proxy/header rewrite layer. - This is a compatibility layer for HTTP headers only; MCP capability/version negotiation still happens in normal
initializeflow.
For client-specific interoperability constraints (for example schema subset
limits, structuredContent shape expectations, protocol-header normalization,
and Accept requirements), see
AI Client Compatibility.
With http_hardening enabled, core can enforce:
- inbound request body cap (
max_request_bytes, HTTP413on exceed), - general inbound rate limiting (
rate_limit, HTTP429on exceed), - session abuse controls (
max_sessions,idle_ttl_secs,max_lifetime_secs,creation_rate), - panic-to-500 and sensitive-header transport guards.
See Transport and Config Schema for field-level behavior and validation rules.
Tool plugins can emit server logs and/or MCP client notifications:
let ctx = params.ctx;
ctx.log_event(rust_mcp_core::LogEventParams {
level: rust_mcp_core::mcp::LoggingLevel::Info,
message: "synced records".to_owned(),
data: Some(serde_json::json!({"count": 42})),
channels: &[rust_mcp_core::LogChannel::Server, rust_mcp_core::LogChannel::Client],
}).await?;LogChannel::Serverwrites to the server's tracing output (controlled byserver.logging.level).LogChannel::Clientsends an MCPnotifications/messageto the client (controlled byclient_logging.levelandlogging/setLevel).logging/setLeveldoes not change server tracing verbosity. It only updates client notification filtering.- On config reload,
server.logging.levelupdates are applied when rust-mcp-core owns the global tracing subscriber. If a host app already set the global subscriber, rust-mcp-core logs a warning and cannot override it.
When http_tools is enabled and an upstream uses auth.type: oauth2, plugins can reuse native token acquisition via PluginContext:
let token = params.ctx.upstream_access_token("partner_api", false).await?;
let (name, value) = params
.ctx
.upstream_bearer_header("partner_api", false)
.await?;upstream_bearer_header returns ("Authorization", "Bearer <token>"). force_refresh=true forces one refresh attempt through the same outbound OAuth2 token manager used by HTTP tools.
For outbound requests to configured upstreams, plugins can use the built-in helper path:
let response = params.ctx.send(
"partner_api",
rust_mcp_core::OutboundHttpRequest {
method: "GET".to_owned(),
url: "/partners".to_owned(),
..rust_mcp_core::OutboundHttpRequest::default()
},
).await?;This path applies the same outbound default-resolution behavior as built-in HTTP tools.
Use send_with(...) for per-call auth overrides and send_raw(...) when you intentionally want to bypass upstream/default/retry policy.
send_with(...) supports PluginSendAuthMode::{Inherit, None, Explicit}.
See examples/plugins-tool-oauth-helper/README.md for a complete runnable flow.
| Document | Description |
|---|---|
| Plugin Guide | Plugin trait signatures/imports, PluginContext behavior (including request_list_refresh), and MCP model cheat sheet |
| Auth | Auth modes, token validation, OAuth metadata, TLS deployment |
| Tasks | Task utility behavior, task_support modes, SDK comparison |
| Transport | Streamable HTTP options, session modes, protocol negotiation, and hardening controls |
| Config Schema | Full field reference, defaults, validation rules, env expansion |
| Config Reload | Consumer-managed reload flow, trigger patterns, and failure semantics |
| AI Client Compatibility | Practical MCP client constraints and compatibility patterns |
| Troubleshooting | Common runtime/client errors and concrete fixes |
Runnable examples live under examples/ with per-example configs in
examples/<name>/config/mcp_config.yml.
- Browse all examples: examples/README.md
- Minimal core: examples/core-minimal/README.md
- Auth flows: auth-bearer, auth-oauth-jwt, auth-oauth-introspection, auth-all-mode
- HTTP tools: tools-web-search, tools-crud-http, tools-http-post, tools-templating, tools-output-modes, tools-upstream-auth, tools-rich-content
- Plugin wiring: plugins-tool-custom, plugins-tool-filesystem, plugins-auth-custom, plugins-router-http, plugins-simple-outbound-http
- Prompts/resources/tasks/client features: prompts-inline-plugin, resources-inline-plugin, resources-subscribe-updated, utility-tasks, utility-tasks-advanced, plugins-tool-client-features, plugins-tool-client-features-advanced
- Utility behavior: utility-logging, utility-progress, utility-cancellation, utility-completion, utility-pagination, utility-list-changed
See the full schema and validation rules in CONFIG_SCHEMA.md.