Expand description
§mcp-skill-framework
A small framework for building Model Context Protocol servers on top
of rmcp as a uniform layer of self-contained tools called skills.
Each tool you expose is a Skill: a type with a name, a description,
a JSON-schema’d argument struct, and an async call body. The framework
adds the cross-cutting machinery every skill ends up wanting:
- Declarative input validation (
validation) — assert ranges, enums, mutual exclusion, regex, and length as data (Rules). The dispatcher evaluates them before your body runs and returns a structured{"validation_failed": [...]}payload a calling model can correct from. - Capability probes (
SkillCapability) — answer “can this host actually run this tool?” (a binary on$PATH, a reachable socket) separately from whether the operator enabled it.capability::resolvecombines each tool’s probe with its family’s (“familyUnavailablewins”), androutes_gatedblocks unavailable tools at dispatch with a reason + hint the caller can act on. - Family metadata (
FamilyMeta) — group related skills, describe them, and probe their shared host requirement as a unit. - Rich descriptions (
describe) — render a skill or family (description, use cases, worked examples, validation rules, argument schema) for an on-demand introspection tool. - A ready-made dispatcher (
route_skill) — adapt aSkillinto anrmcptool route with the validation gate already wired in.
The Skill trait is generic over a server-state type S — whatever
shared state your tools need (HTTP clients, DB handles, config). The
framework never inspects S; it just hands each call a &S.
§Quickstart
use std::sync::Arc;
use futures::future::BoxFuture;
use mcp_skill_framework::{schema_for, text_result, Rule, Skill, SkillCtx, SkillExample};
use rmcp::model::{CallToolResult, JsonObject};
use rmcp::ErrorData as McpError;
use serde::Deserialize;
struct App; // your shared server state
#[derive(Deserialize, schemars::JsonSchema)]
struct FormatArgs {
/// Total seconds to format.
seconds: i64,
/// Output style: `human` or `hms`.
#[serde(default)]
style: Option<String>,
}
struct Format;
impl Skill<App> for Format {
fn name(&self) -> &'static str { "duration_format" }
fn description(&self) -> &'static str { "Format seconds as `human` or `hms`." }
fn schema(&self) -> Arc<JsonObject> { schema_for::<FormatArgs>() }
fn validation_rules(&self) -> &'static [Rule] {
&[Rule::OneOf { field: "style", values: &["human", "hms"] }]
}
fn examples(&self) -> &'static [SkillExample] {
&[SkillExample { title: "HH:MM:SS", args: r#"{"seconds": 9045, "style": "hms"}"#, note: None }]
}
fn call<'a>(&self, ctx: SkillCtx<'a, App>) -> BoxFuture<'a, Result<CallToolResult, McpError>> {
Box::pin(async move {
let (_app, a) = ctx.parse::<FormatArgs>()?;
Ok(text_result(format!("{} seconds", a.seconds)))
})
}
}
// Register with an rmcp ToolRouter:
// let router = rmcp::handler::server::router::tool::ToolRouter::new()
// .with_route(mcp_skill_framework::route_skill(Box::new(Format)));Re-exports§
pub use capability::Capabilities;pub use capability::SkillCapability;pub use dispatch::route_skill;pub use dispatch::route_skill_gated;pub use dispatch::routes_gated;pub use dispatch::with_extra_property;pub use family::FamilyMeta;pub use skill::schema_for;pub use skill::NoArgs;pub use skill::Skill;pub use skill::SkillCtx;pub use skill::SkillExample;pub use validation::evaluate;pub use validation::FieldViolation;pub use validation::Rule;pub use validation::ValidationResult;
Modules§
- capability
- Capability probes — “can this host actually run this tool?”
- describe
- Human-readable rendering of skills and families — “the description” layer.
- dispatch
- Turning skills into rmcp tool routes.
- family
- Family metadata — group related skills and describe them as a unit.
- skill
- The skill contract — the uniform capability unit of an MCP server.
- validation
- Structured input validation for the
Skillcontract.
Functions§
- internal
- Build an
internal_errorMCP error from anythingDisplay. Use for failures that aren’t the caller’s fault (an upstream API, a socket, a parse of data you fetched). - invalid
- Build an
invalid_paramsMCP error from anythingDisplay. Use for bad caller input that the declarativevalidationlayer didn’t catch. - text_
result - Wrap a string as a successful single-text-content tool result. The
idiomatic return for a skill body that produced one textual answer (often
a JSON string built with
serde_json).