use crate::tools;
use serde_json::{Value as JsonValue, json};
pub fn descriptors() -> Vec<JsonValue> {
vec![
json!({
"uri": "noyalib://error-codes",
"name": "error-codes",
"title": "noyalib-mcp error code taxonomy",
"description": "The JSON-RPC error codes this server returns, \
each with its meaning.",
"mimeType": "application/json"
}),
json!({
"uri": "noyalib://tools",
"name": "tools",
"title": "noyalib-mcp tool reference",
"description": "The tool descriptors this server exposes \
(mirrors tools/list).",
"mimeType": "application/json"
}),
]
}
pub fn templates() -> Vec<JsonValue> {
vec![json!({
"uriTemplate": "noyalib://tool/{name}",
"name": "tool",
"title": "noyalib-mcp tool descriptor",
"description": "The descriptor for a single tool by name \
(noyalib_get or noyalib_set).",
"mimeType": "application/json"
})]
}
pub fn read(params: JsonValue) -> Result<JsonValue, (i32, String)> {
let uri = params
.get("uri")
.and_then(|v| v.as_str())
.ok_or_else(|| (-32602, "missing field: uri".to_string()))?;
let text = match uri {
"noyalib://error-codes" => error_codes_json(),
"noyalib://tools" => json!({ "tools": tools::descriptors() }).to_string(),
_ => tool_descriptor_text(uri)?,
};
Ok(json!({
"contents": [
{ "uri": uri, "mimeType": "application/json", "text": text }
]
}))
}
fn tool_descriptor_text(uri: &str) -> Result<String, (i32, String)> {
let name = uri
.strip_prefix("noyalib://tool/")
.ok_or_else(|| (-32002, format!("resource not found: {uri}")))?;
tools::descriptors()
.into_iter()
.find(|d| d["name"].as_str() == Some(name))
.map(|d| d.to_string())
.ok_or_else(|| (-32002, format!("resource not found: {uri}")))
}
fn error_codes_json() -> String {
json!({
"-32000": "file I/O error (read or write failed)",
"-32001": "YAML parse error",
"-32002": "path or resource not found",
"-32003": "set failed (value could not be written at the path)",
"-32600": "invalid request (jsonrpc field must be \"2.0\")",
"-32601": "method not found",
"-32602": "missing or invalid parameter",
"-32700": "JSON parse error"
})
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptors_lists_static_resources() {
let d = descriptors();
let uris: Vec<&str> = d.iter().map(|r| r["uri"].as_str().unwrap()).collect();
assert!(uris.contains(&"noyalib://error-codes"));
assert!(uris.contains(&"noyalib://tools"));
for r in &d {
assert_eq!(r["mimeType"].as_str(), Some("application/json"));
}
}
#[test]
fn templates_lists_the_tool_template() {
let t = templates();
assert_eq!(t.len(), 1);
assert_eq!(t[0]["uriTemplate"].as_str(), Some("noyalib://tool/{name}"));
}
#[test]
fn read_rejects_missing_uri() {
let err = read(json!({})).unwrap_err();
assert_eq!(err.0, -32602);
assert!(err.1.contains("uri"));
}
#[test]
fn read_returns_error_code_taxonomy() {
let v = read(json!({"uri": "noyalib://error-codes"})).unwrap();
let text = v["contents"][0]["text"].as_str().unwrap();
let parsed: JsonValue = serde_json::from_str(text).unwrap();
assert!(parsed["-32001"].is_string());
assert_eq!(
v["contents"][0]["uri"].as_str(),
Some("noyalib://error-codes")
);
}
#[test]
fn read_returns_tool_reference() {
let v = read(json!({"uri": "noyalib://tools"})).unwrap();
let text = v["contents"][0]["text"].as_str().unwrap();
let parsed: JsonValue = serde_json::from_str(text).unwrap();
assert_eq!(
parsed["tools"].as_array().unwrap().len(),
tools::descriptors().len()
);
}
#[test]
fn read_returns_single_tool_descriptor() {
let v = read(json!({"uri": "noyalib://tool/noyalib_get"})).unwrap();
let text = v["contents"][0]["text"].as_str().unwrap();
let parsed: JsonValue = serde_json::from_str(text).unwrap();
assert_eq!(parsed["name"].as_str(), Some("noyalib_get"));
}
#[test]
fn read_rejects_unknown_tool_name() {
let err = read(json!({"uri": "noyalib://tool/frobnicate"})).unwrap_err();
assert_eq!(err.0, -32002);
}
#[test]
fn read_rejects_unknown_uri() {
let err = read(json!({"uri": "noyalib://mystery"})).unwrap_err();
assert_eq!(err.0, -32002);
}
}