use elicitation::ElicitPlugin;
use futures::future::BoxFuture;
use rmcp::{
ErrorData,
model::{CallToolRequestParams, CallToolResult, Content, Tool},
service::RequestContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use tracing::instrument;
use crate::plugins::util::{parse_args, typed_tool};
#[derive(Debug, Deserialize, JsonSchema)]
struct MethodParams {
method: String,
}
pub struct MethodPlugin;
impl ElicitPlugin for MethodPlugin {
fn name(&self) -> &'static str {
"method"
}
fn list_tools(&self) -> Vec<Tool> {
vec![
typed_tool::<MethodParams>(
"from_str",
"Parse and validate an HTTP method string. Returns the normalized uppercase method and its properties (is_safe, is_idempotent), or an error for invalid input.",
),
typed_tool::<MethodParams>(
"as_str",
"Return the canonical uppercase string representation of the method (e.g. \"GET\").",
),
typed_tool::<MethodParams>(
"is_safe",
"Return true if the method is safe (has no intended side effects): GET, HEAD, OPTIONS, TRACE.",
),
typed_tool::<MethodParams>(
"is_idempotent",
"Return true if the method is idempotent (repeated requests have the same effect): GET, HEAD, PUT, DELETE, OPTIONS, TRACE.",
),
]
}
#[instrument(skip(self, _ctx), fields(tool = %params.name))]
fn call_tool<'a>(
&'a self,
params: CallToolRequestParams,
_ctx: RequestContext<rmcp::RoleServer>,
) -> BoxFuture<'a, Result<CallToolResult, ErrorData>> {
Box::pin(async move {
match params.name.as_ref() {
"from_str" => {
let p: MethodParams = parse_args(¶ms)?;
match reqwest::Method::from_bytes(p.method.as_bytes()) {
Ok(m) => {
let json = serde_json::json!({
"method": m.as_str(),
"is_safe": m.is_safe(),
"is_idempotent": m.is_idempotent(),
});
Ok(CallToolResult::success(vec![Content::text(
json.to_string(),
)]))
}
Err(e) => Ok(CallToolResult::error(vec![Content::text(e.to_string())])),
}
}
"as_str" => {
let p: MethodParams = parse_args(¶ms)?;
match reqwest::Method::from_bytes(p.method.as_bytes()) {
Ok(m) => Ok(CallToolResult::success(vec![Content::text(m.as_str())])),
Err(e) => Ok(CallToolResult::error(vec![Content::text(e.to_string())])),
}
}
"is_safe" => bool_tool(¶ms, |m| m.is_safe()),
"is_idempotent" => bool_tool(¶ms, |m| m.is_idempotent()),
other => Err(ErrorData::invalid_params(
format!("unknown tool: {other}"),
None,
)),
}
})
}
}
fn bool_tool(
params: &CallToolRequestParams,
f: impl Fn(reqwest::Method) -> bool,
) -> Result<CallToolResult, ErrorData> {
let p: MethodParams = parse_args(params)?;
match reqwest::Method::from_bytes(p.method.as_bytes()) {
Ok(m) => Ok(CallToolResult::success(vec![Content::text(
f(m).to_string(),
)])),
Err(e) => Ok(CallToolResult::error(vec![Content::text(e.to_string())])),
}
}