use crate::content::{best_practices, cli_guide, sdk_reference};
use async_trait::async_trait;
use pmcp::types::{Content, ListResourcesResult, ReadResourceResult, ResourceInfo};
use pmcp::RequestHandlerExtra;
pub struct DocsResourceHandler;
const DOC_RESOURCES: &[(&str, &str, &str)] = &[
(
"pmcp://docs/typed-tools",
"Typed Tools Guide",
"TypedTool, TypedSyncTool, and TypedToolWithOutput patterns",
),
(
"pmcp://docs/resources",
"Resources Guide",
"ResourceHandler trait, URI patterns, and static content",
),
(
"pmcp://docs/prompts",
"Prompts Guide",
"PromptHandler trait, PromptInfo metadata, and workflow prompts",
),
(
"pmcp://docs/auth",
"Authentication Guide",
"OAuth, API key, and JWT middleware configuration",
),
(
"pmcp://docs/middleware",
"Middleware Guide",
"Tool and protocol middleware composition",
),
(
"pmcp://docs/mcp-apps",
"MCP Apps Guide",
"Widget UIs, _meta emission, and host layer integration",
),
(
"pmcp://docs/error-handling",
"Error Handling Guide",
"Error variants, Result patterns, and error propagation",
),
(
"pmcp://docs/cli",
"CLI Reference",
"cargo-pmcp commands: init, test, preview, deploy, and more",
),
(
"pmcp://docs/best-practices",
"Best Practices",
"Tool design, resource organization, testing, and deployment",
),
];
fn content_for_uri(uri: &str) -> Option<&'static str> {
match uri {
"pmcp://docs/typed-tools" => Some(sdk_reference::TYPED_TOOLS),
"pmcp://docs/resources" => Some(sdk_reference::RESOURCES),
"pmcp://docs/prompts" => Some(sdk_reference::PROMPTS),
"pmcp://docs/auth" => Some(sdk_reference::AUTH),
"pmcp://docs/middleware" => Some(sdk_reference::MIDDLEWARE),
"pmcp://docs/mcp-apps" => Some(sdk_reference::MCP_APPS),
"pmcp://docs/error-handling" => Some(sdk_reference::ERROR_HANDLING),
"pmcp://docs/cli" => Some(cli_guide::GUIDE),
"pmcp://docs/best-practices" => Some(best_practices::BEST_PRACTICES),
_ => None,
}
}
#[async_trait]
impl pmcp::server::ResourceHandler for DocsResourceHandler {
async fn list(
&self,
_cursor: Option<String>,
_extra: RequestHandlerExtra,
) -> pmcp::Result<ListResourcesResult> {
let resources = DOC_RESOURCES
.iter()
.map(|(uri, name, description)| {
ResourceInfo::new(*uri, *name)
.with_description(*description)
.with_mime_type("text/markdown")
})
.collect();
Ok(ListResourcesResult::new(resources))
}
async fn read(
&self,
uri: &str,
_extra: RequestHandlerExtra,
) -> pmcp::Result<ReadResourceResult> {
match content_for_uri(uri) {
Some(text) => Ok(ReadResourceResult::new(vec![Content::Resource {
uri: uri.to_string(),
text: Some(text.to_string()),
mime_type: Some("text/markdown".to_string()),
meta: None,
}])),
None => Err(pmcp::Error::not_found(format!(
"Unknown documentation resource: {uri}"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn doc_resources_has_nine_entries() {
assert_eq!(DOC_RESOURCES.len(), 9);
}
#[test]
fn all_uris_resolve_to_content() {
for (uri, _, _) in DOC_RESOURCES {
assert!(
content_for_uri(uri).is_some(),
"URI {uri} should resolve to content"
);
}
}
#[test]
fn unknown_uri_returns_none() {
assert!(content_for_uri("pmcp://docs/unknown").is_none());
}
}