Skip to main content

pmcp_server/resources/
docs.rs

1//! Documentation resource handler.
2//!
3//! Serves embedded SDK documentation via `pmcp://docs/*` URIs.
4//! All content is compiled into the binary -- no runtime file I/O.
5
6use crate::content::{best_practices, cli_guide, sdk_reference};
7use async_trait::async_trait;
8use pmcp::types::{Content, ListResourcesResult, ReadResourceResult, ResourceInfo};
9use pmcp::RequestHandlerExtra;
10
11/// Resource handler that serves embedded PMCP SDK documentation.
12///
13/// Routes `pmcp://docs/*` URIs to compile-time embedded markdown content.
14/// Returns 9 documentation resources covering the SDK API, CLI, and best practices.
15pub struct DocsResourceHandler;
16
17/// All available documentation resources with their URIs, names, and descriptions.
18const DOC_RESOURCES: &[(&str, &str, &str)] = &[
19    (
20        "pmcp://docs/typed-tools",
21        "Typed Tools Guide",
22        "TypedTool, TypedSyncTool, and TypedToolWithOutput patterns",
23    ),
24    (
25        "pmcp://docs/resources",
26        "Resources Guide",
27        "ResourceHandler trait, URI patterns, and static content",
28    ),
29    (
30        "pmcp://docs/prompts",
31        "Prompts Guide",
32        "PromptHandler trait, PromptInfo metadata, and workflow prompts",
33    ),
34    (
35        "pmcp://docs/auth",
36        "Authentication Guide",
37        "OAuth, API key, and JWT middleware configuration",
38    ),
39    (
40        "pmcp://docs/middleware",
41        "Middleware Guide",
42        "Tool and protocol middleware composition",
43    ),
44    (
45        "pmcp://docs/mcp-apps",
46        "MCP Apps Guide",
47        "Widget UIs, _meta emission, and host layer integration",
48    ),
49    (
50        "pmcp://docs/error-handling",
51        "Error Handling Guide",
52        "Error variants, Result patterns, and error propagation",
53    ),
54    (
55        "pmcp://docs/cli",
56        "CLI Reference",
57        "cargo-pmcp commands: init, test, preview, deploy, and more",
58    ),
59    (
60        "pmcp://docs/best-practices",
61        "Best Practices",
62        "Tool design, resource organization, testing, and deployment",
63    ),
64];
65
66/// Look up the embedded content for a given documentation URI.
67fn content_for_uri(uri: &str) -> Option<&'static str> {
68    match uri {
69        "pmcp://docs/typed-tools" => Some(sdk_reference::TYPED_TOOLS),
70        "pmcp://docs/resources" => Some(sdk_reference::RESOURCES),
71        "pmcp://docs/prompts" => Some(sdk_reference::PROMPTS),
72        "pmcp://docs/auth" => Some(sdk_reference::AUTH),
73        "pmcp://docs/middleware" => Some(sdk_reference::MIDDLEWARE),
74        "pmcp://docs/mcp-apps" => Some(sdk_reference::MCP_APPS),
75        "pmcp://docs/error-handling" => Some(sdk_reference::ERROR_HANDLING),
76        "pmcp://docs/cli" => Some(cli_guide::GUIDE),
77        "pmcp://docs/best-practices" => Some(best_practices::BEST_PRACTICES),
78        _ => None,
79    }
80}
81
82#[async_trait]
83impl pmcp::server::ResourceHandler for DocsResourceHandler {
84    async fn list(
85        &self,
86        _cursor: Option<String>,
87        _extra: RequestHandlerExtra,
88    ) -> pmcp::Result<ListResourcesResult> {
89        let resources = DOC_RESOURCES
90            .iter()
91            .map(|(uri, name, description)| ResourceInfo {
92                uri: (*uri).to_string(),
93                name: (*name).to_string(),
94                description: Some((*description).to_string()),
95                mime_type: Some("text/markdown".to_string()),
96                meta: None,
97            })
98            .collect();
99        Ok(ListResourcesResult::new(resources))
100    }
101
102    async fn read(
103        &self,
104        uri: &str,
105        _extra: RequestHandlerExtra,
106    ) -> pmcp::Result<ReadResourceResult> {
107        match content_for_uri(uri) {
108            Some(text) => Ok(ReadResourceResult::new(vec![Content::Resource {
109                uri: uri.to_string(),
110                text: Some(text.to_string()),
111                mime_type: Some("text/markdown".to_string()),
112                meta: None,
113            }])),
114            None => Err(pmcp::Error::not_found(format!(
115                "Unknown documentation resource: {uri}"
116            ))),
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn doc_resources_has_nine_entries() {
127        assert_eq!(DOC_RESOURCES.len(), 9);
128    }
129
130    #[test]
131    fn all_uris_resolve_to_content() {
132        for (uri, _, _) in DOC_RESOURCES {
133            assert!(
134                content_for_uri(uri).is_some(),
135                "URI {uri} should resolve to content"
136            );
137        }
138    }
139
140    #[test]
141    fn unknown_uri_returns_none() {
142        assert!(content_for_uri("pmcp://docs/unknown").is_none());
143    }
144}