1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! The skill contract — the uniform capability unit of an MCP server.
//!
//! Every tool a server exposes is a **skill**: a self-contained type that
//! implements [`Skill`] (`name` / `description` / `schema` / `call`, plus
//! optional metadata). The trait is generic over a server-state type `S`,
//! which is whatever shared state your tools need — HTTP clients, database
//! handles, configuration. The framework never looks inside `S`; it only
//! hands each call a `&S` so the tool body can use it.
//!
//! ```no_run
//! use std::sync::Arc;
//! use futures::future::BoxFuture;
//! use mcp_skill_framework::{schema_for, text_result, Skill, SkillCtx};
//! use rmcp::model::{CallToolResult, JsonObject};
//! use rmcp::ErrorData as McpError;
//! use serde::Deserialize;
//!
//! struct App; // your shared server state
//!
//! #[derive(Deserialize, schemars::JsonSchema)]
//! struct GreetArgs {
//! /// Who to greet.
//! name: String,
//! }
//!
//! struct Greet;
//! impl Skill<App> for Greet {
//! fn name(&self) -> &'static str { "greet" }
//! fn description(&self) -> &'static str { "Greet someone by name." }
//! fn schema(&self) -> Arc<JsonObject> { schema_for::<GreetArgs>() }
//! fn call<'a>(&self, ctx: SkillCtx<'a, App>) -> BoxFuture<'a, Result<CallToolResult, McpError>> {
//! Box::pin(async move {
//! let (_app, args) = ctx.parse::<GreetArgs>()?;
//! Ok(text_result(format!("Hello, {}!", args.name)))
//! })
//! }
//! }
//! ```
use Arc;
use BoxFuture;
use ;
use ;
use ErrorData as McpError;
use JsonSchema;
use DeserializeOwned;
use crateSkillCapability;
use crate;
/// What a [`Skill::call`] receives: a borrow of the shared server state plus
/// the raw, already-extracted argument object (parse it with
/// [`SkillCtx::parse`]).
///
/// `peer` + `meta` mirror the rmcp request context for the underlying tool
/// call. Skills that emit progress notifications read the caller's
/// `progressToken` out of `meta` and use `peer` to send them; plain
/// synchronous tools ignore both. They are owned rather than borrowed
/// because `Peer` is cheap to clone (transport-channel handles only) and a
/// skill routinely needs to hand it into a spawned task that outlives the
/// call.
/// One concrete worked example for a [`Skill`]. Surfaced through an
/// introspection tool (see [`crate::describe`]); not part of the MCP
/// `tools/list` payload, which stays tight (just `description` and
/// `inputSchema`) so the orientation is paid for once and looked up on
/// demand thereafter.
/// The contract every tool implements. Object-safe, so skills are stored as
/// `Box<dyn Skill<S>>` and assembled uniformly. Generic over the shared
/// server-state type `S`.
/// Build a JSON schema for an arguments struct (helper for [`Skill::schema`]).
///
/// The struct must derive [`schemars::JsonSchema`] (and usually
/// [`serde::Deserialize`] so [`SkillCtx::parse`] can read it). Per-field
/// `///` doc comments become the schema's property descriptions, which is
/// what a model reads to fill the arguments.
/// Empty argument set, for skills that take no parameters.