diff --git a/README.md b/README.md index 6277207b..46136560 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ This framework provides everything you need to build production-ready MCP servers in Rust. It's been developed and proven through a real-world home automation server with 30+ tools that successfully integrates with MCP Inspector, Claude Desktop, and HTTP clients. -**🎉 NEW: MCP Apps Extension Support** - First production Rust framework supporting [SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) for interactive HTML user interfaces! +**🎉 MCP 2025-11-25 Support** - Full implementation of the latest MCP specification including Tasks, Tool Calling in Sampling, and Enhanced Elicitation! + +**🖼️ MCP Apps Extension Support** - First production Rust framework supporting [SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) for interactive HTML user interfaces! ## What is MCP? @@ -29,8 +31,8 @@ Add to your `Cargo.toml`: ```toml [dependencies] -pulseengine-mcp-server = "0.4.1" -pulseengine-mcp-protocol = "0.4.1" +pulseengine-mcp-server = "0.15" +pulseengine-mcp-protocol = "0.15" tokio = { version = "1.0", features = ["full"] } async-trait = "0.1" ``` @@ -56,16 +58,16 @@ impl McpBackend for MyBackend { fn get_server_info(&self) -> ServerInfo { ServerInfo { - protocol_version: ProtocolVersion::default(), - capabilities: ServerCapabilities { - tools: Some(ToolsCapability { list_changed: Some(false) }), - ..Default::default() - }, - server_info: Implementation { - name: "My MCP Server".to_string(), - version: "1.0.0".to_string(), - }, - instructions: Some("A simple example server".to_string()), + protocol_version: ProtocolVersion::default(), // MCP 2025-11-25 + capabilities: ServerCapabilities::builder() + .enable_tools() + .build(), + server_info: Implementation::with_description( + "My MCP Server", + "1.0.0", + "A simple example server", + ), + instructions: Some("Use 'hello' tool to greet someone".to_string()), } } @@ -82,9 +84,15 @@ impl McpBackend for MyBackend { }, "required": ["name"] }), + output_schema: None, + title: None, + annotations: None, + icons: None, + execution: None, + _meta: None, } ], - next_cursor: String::new(), + next_cursor: None, }) } @@ -96,10 +104,7 @@ impl McpBackend for MyBackend { .and_then(|v| v.as_str()) .unwrap_or("World"); - Ok(CallToolResult { - content: vec![Content::text(format!("Hello, {}!", name))], - is_error: Some(false), - }) + Ok(CallToolResult::text(format!("Hello, {}!", name))) } _ => Err("Unknown tool".into()), } @@ -107,13 +112,13 @@ impl McpBackend for MyBackend { // Simple implementations for unused features async fn list_resources(&self, _: PaginatedRequestParam) -> Result { - Ok(ListResourcesResult { resources: vec![], next_cursor: String::new() }) + Ok(ListResourcesResult { resources: vec![], next_cursor: None }) } async fn read_resource(&self, _: ReadResourceRequestParam) -> Result { Err("No resources".into()) } async fn list_prompts(&self, _: PaginatedRequestParam) -> Result { - Ok(ListPromptsResult { prompts: vec![], next_cursor: String::new() }) + Ok(ListPromptsResult { prompts: vec![], next_cursor: None }) } async fn get_prompt(&self, _: GetPromptRequestParam) -> Result { Err("No prompts".into()) @@ -163,23 +168,19 @@ async fn main() -> Result<(), Box> { - Request size limits and parameter validation - CORS policies and security headers -### 📊 [mcp-monitoring](mcp-monitoring/) - Observability - -- Health checks and metrics collection -- Performance tracking and request tracing -- Integration with monitoring systems - ### 📝 [mcp-logging](mcp-logging/) - Structured Logging - JSON logging with correlation IDs - Automatic credential sanitization +- MCP `logging/setLevel` conformance - Security audit trails -### 🖥️ [mcp-cli](mcp-cli/) & [mcp-cli-derive](mcp-cli-derive/) - CLI Integration +### ⚙️ [mcp-macros](mcp-macros/) - Procedural Macros -- Command-line interface generation -- Configuration management -- Derive macros for backends +- `#[mcp_server]` - Generate server boilerplate +- `#[mcp_tool]` - Define tools with schema generation +- `#[mcp_resource]` - Define parameterized resources +- `#[mcp_backend]` - Derive backend implementations ## Examples @@ -187,7 +188,11 @@ async fn main() -> Result<(), Box> { Complete minimal MCP server demonstrating basic concepts. -### 🎨 [UI-Enabled Server](examples/ui-enabled-server/) **NEW!** +### 🔐 [Hello World with Auth](examples/hello-world-with-auth/) + +MCP server with full authentication and authorization. + +### 🎨 [UI-Enabled Server](examples/ui-enabled-server/) **MCP Apps Extension demonstration** with interactive HTML interfaces: @@ -196,13 +201,13 @@ Complete minimal MCP server demonstrating basic concepts. - `text/html+mcp` MIME type - Complete testing guide -### 🏗️ [Backend Example](examples/backend-example/) +### 📁 [Resources Demo](examples/resources-demo/) -Shows advanced backend implementation patterns. +Demonstrates `#[mcp_resource]` macro for parameterized resources with URI templates. -### 🖥️ [CLI Example](examples/cli-example/) +### ⚡ [Ultra Simple](examples/ultra-simple/) -Demonstrates CLI integration and configuration. +The absolute minimum MCP server implementation. ### 🏠 Real-World Reference: Loxone MCP Server diff --git a/docs/MACRO_GUIDE.md b/docs/MACRO_GUIDE.md index 55b0f297..2b7ffbee 100644 --- a/docs/MACRO_GUIDE.md +++ b/docs/MACRO_GUIDE.md @@ -14,7 +14,7 @@ Add PulseEngine MCP Macros to your `Cargo.toml`: ```toml [dependencies] -pulseengine-mcp-macros = "0.6" +pulseengine-mcp-macros = "0.15" tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } ``` diff --git a/examples/resources-demo/src/main.rs b/examples/resources-demo/src/main.rs index 90982240..3a95a5d3 100644 --- a/examples/resources-demo/src/main.rs +++ b/examples/resources-demo/src/main.rs @@ -1,13 +1,13 @@ //! # MCP Resources Demo //! -//! This example demonstrates how to use the #[mcp_resource] attribute inside -//! #[mcp_tools] impl blocks to create dynamic, parameterized resources. +//! This example demonstrates how to use the `#[mcp_resource]` attribute inside +//! `#[mcp_tools]` impl blocks to create dynamic, parameterized resources. //! //! ## Key Concepts //! //! 1. **Tools vs Resources**: -//! - Methods WITHOUT #[mcp_resource] → become tools -//! - Methods WITH #[mcp_resource] → become resources +//! - Methods WITHOUT `#[mcp_resource]` → become tools +//! - Methods WITH `#[mcp_resource]` → become resources //! //! 2. **URI Templates**: //! - Resources use URI templates with parameters: "scheme://{param1}/{param2}" diff --git a/mcp-auth/src/oauth/client_metadata.rs b/mcp-auth/src/oauth/client_metadata.rs index 21c8ef4d..6d4584db 100644 --- a/mcp-auth/src/oauth/client_metadata.rs +++ b/mcp-auth/src/oauth/client_metadata.rs @@ -7,7 +7,7 @@ //! registration approach for MCP 2025-11-25. //! //! # Reference -//! https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00 +//! use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/mcp-auth/src/oauth/metadata.rs b/mcp-auth/src/oauth/metadata.rs index fcf19592..8ee80b25 100644 --- a/mcp-auth/src/oauth/metadata.rs +++ b/mcp-auth/src/oauth/metadata.rs @@ -80,7 +80,7 @@ pub async fn authorization_server_metadata() -> impl IntoResponse { /// - MUST include code_challenge_methods_supported for MCP compatibility /// /// # Reference -/// https://openid.net/specs/openid-connect-discovery-1_0.html +/// pub async fn openid_configuration() -> impl IntoResponse { let base_url = get_base_url(); diff --git a/mcp-auth/src/oauth/mod.rs b/mcp-auth/src/oauth/mod.rs index d15785d3..2732e016 100644 --- a/mcp-auth/src/oauth/mod.rs +++ b/mcp-auth/src/oauth/mod.rs @@ -16,7 +16,7 @@ //! - SHOULD support Client ID Metadata Documents for registration //! - MAY support Dynamic Client Registration (for backwards compatibility) //! -//! Reference: https://github.com/shuttle-hq/shuttle-examples/tree/main/mcp/mcp-sse-oauth +//! Reference: pub mod authorize; pub mod bearer; diff --git a/mcp-server/src/handler.rs b/mcp-server/src/handler.rs index 1ee8d7d0..b6a3ebe5 100644 --- a/mcp-server/src/handler.rs +++ b/mcp-server/src/handler.rs @@ -71,7 +71,7 @@ pub struct GenericServerHandler { middleware: MiddlewareStack, /// Global subscription registry tracking subscribed resource URIs /// Note: This is a simplified global implementation. For per-client - /// subscriptions, use a HashMap> instead. + /// subscriptions, use a `HashMap>` instead. subscriptions: Arc>>, } diff --git a/mcp-server/src/lib.rs b/mcp-server/src/lib.rs index fcfd2689..2aa6d45a 100644 --- a/mcp-server/src/lib.rs +++ b/mcp-server/src/lib.rs @@ -24,12 +24,15 @@ //! //! fn get_server_info(&self) -> ServerInfo { //! ServerInfo { -//! protocol_version: ProtocolVersion::default(), -//! capabilities: ServerCapabilities::default(), -//! server_info: Implementation { -//! name: "My Server".to_string(), -//! version: "1.0.0".to_string(), -//! }, +//! protocol_version: ProtocolVersion::default(), // MCP 2025-11-25 +//! capabilities: ServerCapabilities::builder() +//! .enable_tools() +//! .build(), +//! server_info: Implementation::with_description( +//! "My Server", +//! "1.0.0", +//! "Example MCP server", +//! ), //! instructions: Some("Example server".to_string()), //! } //! } @@ -39,18 +42,18 @@ //! } //! //! async fn call_tool(&self, _: CallToolRequestParam) -> Result { -//! Ok(CallToolResult { content: vec![], is_error: Some(false) }) +//! Ok(CallToolResult::empty()) //! } //! //! // Implement other required methods (simplified for example) //! # async fn list_resources(&self, _: PaginatedRequestParam) -> Result { -//! # Ok(ListResourcesResult { resources: vec![], next_cursor: String::new() }) +//! # Ok(ListResourcesResult { resources: vec![], next_cursor: None }) //! # } //! # async fn read_resource(&self, _: ReadResourceRequestParam) -> Result { //! # Err("No resources".into()) //! # } //! # async fn list_prompts(&self, _: PaginatedRequestParam) -> Result { -//! # Ok(ListPromptsResult { prompts: vec![], next_cursor: String::new() }) +//! # Ok(ListPromptsResult { prompts: vec![], next_cursor: None }) //! # } //! # async fn get_prompt(&self, _: GetPromptRequestParam) -> Result { //! # Err("No prompts".into())