Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 40 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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"
```
Expand All @@ -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()),
}
}

Expand All @@ -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,
})
}

Expand All @@ -96,24 +104,21 @@ 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()),
}
}

// Simple implementations for unused features
async fn list_resources(&self, _: PaginatedRequestParam) -> Result<ListResourcesResult, Self::Error> {
Ok(ListResourcesResult { resources: vec![], next_cursor: String::new() })
Ok(ListResourcesResult { resources: vec![], next_cursor: None })
}
async fn read_resource(&self, _: ReadResourceRequestParam) -> Result<ReadResourceResult, Self::Error> {
Err("No resources".into())
}
async fn list_prompts(&self, _: PaginatedRequestParam) -> Result<ListPromptsResult, Self::Error> {
Ok(ListPromptsResult { prompts: vec![], next_cursor: String::new() })
Ok(ListPromptsResult { prompts: vec![], next_cursor: None })
}
async fn get_prompt(&self, _: GetPromptRequestParam) -> Result<GetPromptResult, Self::Error> {
Err("No prompts".into())
Expand Down Expand Up @@ -163,31 +168,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
- 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

### 🌍 [Hello World](examples/hello-world/)

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:

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/MACRO_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
```
Expand Down
8 changes: 4 additions & 4 deletions examples/resources-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -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}"
Expand Down
2 changes: 1 addition & 1 deletion mcp-auth/src/oauth/client_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//! <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00>

use serde::{Deserialize, Serialize};
use thiserror::Error;
Expand Down
2 changes: 1 addition & 1 deletion mcp-auth/src/oauth/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// <https://openid.net/specs/openid-connect-discovery-1_0.html>
pub async fn openid_configuration() -> impl IntoResponse {
let base_url = get_base_url();

Expand Down
2 changes: 1 addition & 1 deletion mcp-auth/src/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://github.com/shuttle-hq/shuttle-examples/tree/main/mcp/mcp-sse-oauth>

pub mod authorize;
pub mod bearer;
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub struct GenericServerHandler<B: McpBackend> {
middleware: MiddlewareStack,
/// Global subscription registry tracking subscribed resource URIs
/// Note: This is a simplified global implementation. For per-client
/// subscriptions, use a HashMap<ClientId, HashSet<String>> instead.
/// subscriptions, use a `HashMap<ClientId, HashSet<String>>` instead.
subscriptions: Arc<RwLock<HashSet<String>>>,
}

Expand Down
21 changes: 12 additions & 9 deletions mcp-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
//! }
//! }
Expand All @@ -39,18 +42,18 @@
//! }
//!
//! async fn call_tool(&self, _: CallToolRequestParam) -> Result<CallToolResult, Self::Error> {
//! 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<ListResourcesResult, Self::Error> {
//! # Ok(ListResourcesResult { resources: vec![], next_cursor: String::new() })
//! # Ok(ListResourcesResult { resources: vec![], next_cursor: None })
//! # }
//! # async fn read_resource(&self, _: ReadResourceRequestParam) -> Result<ReadResourceResult, Self::Error> {
//! # Err("No resources".into())
//! # }
//! # async fn list_prompts(&self, _: PaginatedRequestParam) -> Result<ListPromptsResult, Self::Error> {
//! # Ok(ListPromptsResult { prompts: vec![], next_cursor: String::new() })
//! # Ok(ListPromptsResult { prompts: vec![], next_cursor: None })
//! # }
//! # async fn get_prompt(&self, _: GetPromptRequestParam) -> Result<GetPromptResult, Self::Error> {
//! # Err("No prompts".into())
Expand Down