First tool wired — `schema` (no input args, returns schema.v1 JSON mirroring `kebab schema --json`). Establishes the per-tool module pattern (crates/kebab-mcp/src/tools/<name>.rs) + error helper that maps anyhow::Error to MCP CallToolResult.error with error.v1 content. Dispatch pattern: manual dispatch — explicit `list_tools` + `call_tool` overrides on `impl ServerHandler for KebabHandler` with a `match request.name.as_ref()` arm per tool. No proc-macro magic. Tasks 5-7 should add a new arm + new tools/<name>.rs following the same pattern; also add a `Tool::new(...)` entry in `list_tools`. API shapes confirmed from rmcp 1.6 source: - Content = Annotated<RawContent>; text via `Content::text(s)`; pattern match via `&content.raw` → `RawContent::Text(t)` → `t.text` - CallToolResult::success(Vec<Content>) / ::error(Vec<Content>) - ListToolsResult::with_all_items(Vec<Tool>) - schema_for_empty_input() from rmcp::handler::server::common Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
23 lines
859 B
Rust
23 lines
859 B
Rust
//! Map `anyhow::Error` returned by kebab-app facades to MCP
|
|
//! `CallToolResult` with `isError: true` + error.v1 JSON content.
|
|
|
|
use rmcp::model::{CallToolResult, Content};
|
|
|
|
use kebab_app::classify;
|
|
|
|
/// Convert an `anyhow::Error` to a `CallToolResult` with `isError: true`
|
|
/// and the serialised `error.v1` envelope as the text content.
|
|
pub fn to_tool_error(err: &anyhow::Error) -> CallToolResult {
|
|
let v1 = classify(err, false);
|
|
let body = serde_json::to_string(&v1).unwrap_or_else(|_| {
|
|
r#"{"schema_version":"error.v1","code":"generic","message":"serialize failed"}"#
|
|
.to_string()
|
|
});
|
|
CallToolResult::error(vec![Content::text(body)])
|
|
}
|
|
|
|
/// Wrap a successful wire-schema JSON string as a `CallToolResult`.
|
|
pub fn to_tool_success(json: String) -> CallToolResult {
|
|
CallToolResult::success(vec![Content::text(json)])
|
|
}
|