mcpkit_actix/
lib.rs

1//! Actix-web integration for the Rust MCP SDK.
2//!
3//! This crate provides integration between the MCP SDK and the Actix-web framework,
4//! making it easy to expose MCP servers over HTTP.
5//!
6//! # Features
7//!
8//! - HTTP POST endpoint for JSON-RPC messages
9//! - Server-Sent Events (SSE) streaming for notifications
10//! - Session management with automatic cleanup
11//! - Protocol version validation
12//! - CORS support
13//!
14//! # HTTP Protocol Requirements
15//!
16//! Clients must include the `Mcp-Protocol-Version` header in all requests:
17//!
18//! ```text
19//! POST /mcp HTTP/1.1
20//! Content-Type: application/json
21//! Mcp-Protocol-Version: 2025-11-25
22//!
23//! {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
24//! ```
25//!
26//! Supported protocol versions: `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25`
27//!
28//! # Quick Start
29//!
30//! ```ignore
31//! use mcpkit::prelude::*;
32//! use mcpkit_actix::{McpState, handle_mcp_post, handle_sse};
33//! use actix_web::{web, App, HttpServer};
34//!
35//! // Your MCP server handler (use #[mcp_server] macro)
36//! #[mcp_server(name = "my-server", version = "1.0.0")]
37//! impl MyServer {
38//!     #[tool(description = "Say hello")]
39//!     async fn hello(&self, name: String) -> ToolOutput {
40//!         ToolOutput::text(format!("Hello, {name}!"))
41//!     }
42//! }
43//!
44//! #[actix_web::main]
45//! async fn main() -> std::io::Result<()> {
46//!     // Create MCP state - no Clone required on handler!
47//!     let state = McpState::new(MyServer::new());
48//!
49//!     HttpServer::new(move || {
50//!         App::new()
51//!             .app_data(web::Data::new(state.clone()))
52//!             .route("/mcp", web::post().to(handle_mcp_post::<MyServer>))
53//!             .route("/mcp/sse", web::get().to(handle_sse::<MyServer>))
54//!     })
55//!     .bind("0.0.0.0:3000")?
56//!     .run()
57//!     .await
58//! }
59//! ```
60//!
61//! # Client Example (curl)
62//!
63//! ```bash
64//! # Initialize the connection
65//! curl -X POST http://localhost:3000/mcp \
66//!   -H "Content-Type: application/json" \
67//!   -H "Mcp-Protocol-Version: 2025-11-25" \
68//!   -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","clientInfo":{"name":"test","version":"1.0"},"capabilities":{}}}'
69//!
70//! # List available tools
71//! curl -X POST http://localhost:3000/mcp \
72//!   -H "Content-Type: application/json" \
73//!   -H "Mcp-Protocol-Version: 2025-11-25" \
74//!   -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
75//!
76//! # Call a tool
77//! curl -X POST http://localhost:3000/mcp \
78//!   -H "Content-Type: application/json" \
79//!   -H "Mcp-Protocol-Version: 2025-11-25" \
80//!   -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"hello","arguments":{"name":"World"}}}'
81//! ```
82
83#![deny(missing_docs)]
84
85mod error;
86mod handler;
87mod router;
88mod session;
89mod state;
90
91pub use error::ExtensionError;
92pub use handler::{handle_mcp_post, handle_oauth_protected_resource, handle_sse};
93pub use router::McpRouter;
94pub use session::{
95    EventStore, EventStoreConfig, Session, SessionManager, SessionStore, StoredEvent,
96};
97pub use state::{McpState, OAuthState};
98
99/// Prelude module for convenient imports.
100///
101/// # Example
102///
103/// ```ignore
104/// use mcpkit_actix::prelude::*;
105/// ```
106pub mod prelude {
107    pub use crate::error::ExtensionError;
108    pub use crate::handler::{handle_mcp_post, handle_oauth_protected_resource, handle_sse};
109    pub use crate::router::McpRouter;
110    pub use crate::session::{
111        EventStore, EventStoreConfig, Session, SessionManager, SessionStore, StoredEvent,
112    };
113    pub use crate::state::{McpState, OAuthState};
114}
115
116/// Protocol versions supported by this extension.
117pub const SUPPORTED_VERSIONS: &[&str] = &["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
118
119/// Check if a protocol version is supported.
120#[must_use]
121pub fn is_supported_version(version: Option<&str>) -> bool {
122    version.is_some_and(|v| SUPPORTED_VERSIONS.contains(&v))
123}