airsprotocols_mcp/lib.rs
1//! AIRS MCP - Model Context Protocol Implementation
2//!
3//! This crate provides a complete implementation of the Model Context Protocol (MCP)
4//! built on a solid JSON-RPC 2.0 foundation with trait-based message abstractions.
5//!
6//! # Architecture
7//!
8//! The AIRS MCP implementation is organized in layers:
9//!
10//! - **Protocol Layer** (`protocol`): Unified JSON-RPC 2.0 + MCP protocol implementation
11//! - **Transport Layer** (`transport`): Communication transport abstractions and implementations
12//! - **Integration Layer** (`integration`): High-level MCP client and server interfaces
13//! - **Providers Layer** (`providers`): Production-ready MCP provider implementations
14//! - **Shared Layer** (`shared`): Additional MCP message structures and content types
15//!
16//! # Core Features
17//!
18//! ## JSON-RPC 2.0 Foundation
19//!
20//! Complete JSON-RPC 2.0 specification compliance with:
21//! - Type-safe message structures (`JsonRpcRequest`, `JsonRpcResponse`, `JsonRpcNotification`)
22//! - Flexible request ID support (string and numeric variants)
23//! - Trait-based serialization with consistent behavior across all message types
24//! - Comprehensive validation and error handling
25//!
26//! ## Quick Start
27//!
28//! ```rust
29//! use airsprotocols_mcp::{JsonRpcRequest, JsonRpcMessageTrait, RequestId};
30//! use serde_json::json;
31//!
32//! // Create a JSON-RPC request
33//! let request = JsonRpcRequest::new(
34//! "ping",
35//! Some(json!({"message": "hello world"})),
36//! RequestId::new_string("req-001")
37//! );
38//!
39//! // Serialize using trait method
40//! let json = request.to_json().unwrap();
41//! println!("Request: {}", json);
42//!
43//! // Deserialize back to typed structure
44//! let parsed = JsonRpcRequest::from_json(&json).unwrap();
45//! assert_eq!(request, parsed);
46//! ```
47//!
48//! ## Message Types
49//!
50//! ### JsonRpcRequest
51//! Represents a request to invoke a method on the remote peer:
52//!
53//! ```rust
54//! use airsprotocols_mcp::{JsonRpcRequest, RequestId};
55//! use serde_json::json;
56//!
57//! let request = JsonRpcRequest::new(
58//! "calculate",
59//! Some(json!({"operation": "add", "values": [1, 2, 3]})),
60//! RequestId::new_number(42)
61//! );
62//! ```
63//!
64//! ### JsonRpcResponse
65//! Represents a response to a JSON-RPC request (success or error):
66//!
67//! ```rust
68//! use airsprotocols_mcp::{JsonRpcResponse, RequestId};
69//! use serde_json::json;
70//!
71//! // Success response
72//! let success = JsonRpcResponse::success(
73//! json!({"result": "calculation complete", "value": 6}),
74//! RequestId::new_number(42)
75//! );
76//!
77//! // Error response
78//! let error = JsonRpcResponse::error(
79//! json!({"code": -32602, "message": "Invalid params"}),
80//! Some(RequestId::new_number(42))
81//! );
82//! ```
83//!
84//! ### JsonRpcNotification
85//! Represents a notification (one-way message without response):
86//!
87//! ```rust
88//! use airsprotocols_mcp::JsonRpcNotification;
89//! use serde_json::json;
90//!
91//! let notification = JsonRpcNotification::new(
92//! "user_logged_in",
93//! Some(json!({"user_id": 12345, "timestamp": "2025-07-28T10:30:00Z"}))
94//! );
95//! ```
96//!
97//! ## JsonRpcMessage Trait
98//!
99//! All message types implement the `JsonRpcMessage` trait for consistent serialization:
100//!
101//! ```rust
102//! use airsprotocols_mcp::{JsonRpcMessageTrait, JsonRpcNotification};
103//!
104//! let notification = JsonRpcNotification::new("heartbeat", None);
105//!
106//! // Standard JSON serialization
107//! let json = notification.to_json().unwrap();
108//!
109//! // Pretty-printed JSON for debugging
110//! let pretty = notification.to_json_pretty().unwrap();
111//!
112//! // Deserialize from JSON string
113//! let parsed = JsonRpcNotification::from_json(&json).unwrap();
114//! ```
115//!
116//! ## Request ID Flexibility
117//!
118//! Request IDs support both string and numeric formats per JSON-RPC 2.0 specification:
119//!
120//! ```rust
121//! use airsprotocols_mcp::RequestId;
122//!
123//! // String-based IDs (UUIDs, custom formats, etc.)
124//! let string_id = RequestId::new_string("req-12345-abcdef");
125//!
126//! // Numeric IDs (counters, timestamps, etc.)
127//! let numeric_id = RequestId::new_number(1234567890);
128//!
129//! // Display formatting works for both
130//! println!("String ID: {}", string_id); // "req-12345-abcdef"
131//! println!("Numeric ID: {}", numeric_id); // "1234567890"
132//! ```
133//!
134//! # Error Handling
135//!
136//! All serialization operations return `Result` types with `serde_json::Error`:
137//!
138//! ```rust
139//! use airsprotocols_mcp::{JsonRpcRequest, JsonRpcMessageTrait, RequestId};
140//!
141//! let request = JsonRpcRequest::new("test", None, RequestId::new_number(1));
142//!
143//! match request.to_json() {
144//! Ok(json) => println!("Serialized: {}", json),
145//! Err(e) => eprintln!("Serialization failed: {}", e),
146//! }
147//! ```
148//!
149//! # JSON-RPC 2.0 Specification Compliance
150//!
151//! This implementation strictly adheres to the JSON-RPC 2.0 specification:
152//!
153//! - All messages include `"jsonrpc": "2.0"` field
154//! - Requests have `method`, optional `params`, and `id` fields
155//! - Responses have either `result` or `error` (mutual exclusion), and `id` fields
156//! - Notifications have `method` and optional `params` (no `id` field)
157//! - Request IDs support string, number, and null values
158//! - Parameters must be structured (Object) or ordered (Array) values
159//!
160//! # Future Features
161//!
162//! Planned extensions include:
163//! - MCP protocol layer implementation
164//! - Transport abstractions (STDIO)
165//! - Request correlation and bidirectional communication
166//! - High-level client and server interfaces
167//! - Performance optimizations and zero-copy processing
168//!
169//! # Performance Characteristics
170//!
171//! The current implementation prioritizes correctness and maintainability:
172//! - Message serialization: Sub-millisecond for typical message sizes
173//! - Memory usage: Minimal allocations with efficient serde integration
174//! - Trait-based abstractions: Zero runtime cost through compile-time optimization
175//!
176//! For high-throughput scenarios, future versions will include zero-copy
177//! optimizations and buffer pooling strategies.
178
179// Integration layer modules
180pub mod integration;
181
182// OAuth 2.1 authentication module
183pub mod oauth2;
184
185// Multi-method authentication module
186pub mod authentication;
187
188// Zero-cost generic authorization module
189pub mod authorization;
190
191// Providers layer modules
192pub mod providers;
193
194// Protocol layer modules (TASK-028 consolidation)
195pub mod protocol;
196
197// Transport layer modules
198pub mod transport;
199
200// Re-export commonly used types for convenience
201// This allows users to import directly from the crate root
202pub use protocol::{
203 Base64Data,
204 ClientInfo,
205 JsonRpcError,
206 // JSON-RPC 2.0 Message Types
207 JsonRpcMessage,
208 JsonRpcMessageTrait,
209 JsonRpcNotification,
210 JsonRpcRequest,
211 JsonRpcResponse,
212 MessageContext,
213 MessageHandler,
214 MimeType,
215 // Error Types
216 ProtocolError,
217 ProtocolResult,
218
219 ProtocolVersion,
220 RequestId,
221
222 ServerConfig, // New location of core MCP configuration
223 ServerInfo,
224 // Transport Abstractions
225 Transport as ProtocolTransport,
226 TransportError as ProtocolTransportError,
227
228 // MCP Protocol Types
229 Uri,
230};
231
232// Re-export integration types for convenience
233pub use integration::{
234 IntegrationError, IntegrationResult, McpClient, McpClientBuilder, McpClientConfig, McpError,
235 McpResult, McpServer, McpSessionState,
236};
237
238// Re-export transport types for convenience
239pub use transport::adapters::StdioTransport;
240pub use transport::TransportError;
241
242// Version information
243pub const VERSION: &str = env!("CARGO_PKG_VERSION");
244
245/// Get the crate version as a string
246///
247/// # Examples
248///
249/// ```rust
250/// println!("AIRS MCP version: {}", airsprotocols_mcp::version());
251/// ```
252pub fn version() -> &'static str {
253 VERSION
254}
255
256#[cfg(test)]
257mod integration_tests {
258 use super::*;
259 use serde_json::json;
260
261 #[test]
262 fn test_crate_public_api() {
263 // Test that all core types are accessible from crate root
264 let request = JsonRpcRequest::new(
265 "test_method",
266 Some(json!({"param": "value"})),
267 RequestId::new_string("test-123"),
268 );
269
270 let response =
271 JsonRpcResponse::success(json!({"result": "success"}), RequestId::new_number(456));
272
273 let notification =
274 JsonRpcNotification::new("test_event", Some(json!({"event": "occurred"})));
275
276 // Verify trait methods work through re-exports
277 assert!(request.to_json().is_ok());
278 assert!(response.to_json().is_ok());
279 assert!(notification.to_json().is_ok());
280 }
281
282 #[test]
283 fn test_round_trip_serialization() {
284 // Test complete serialization round-trip through public API
285 let original = JsonRpcRequest::new(
286 "echo",
287 Some(json!([1, 2, 3])),
288 RequestId::new_string("echo-001"),
289 );
290
291 let json = original.to_json().unwrap();
292 let parsed = JsonRpcRequest::from_json(&json).unwrap();
293
294 assert_eq!(original, parsed);
295 }
296
297 #[test]
298 fn test_request_id_types() {
299 // Test both RequestId variants work through public API
300 let string_request = JsonRpcRequest::new("test", None, RequestId::new_string("uuid-12345"));
301
302 let numeric_request = JsonRpcRequest::new("test", None, RequestId::new_number(67890));
303
304 let string_json = string_request.to_json().unwrap();
305 let numeric_json = numeric_request.to_json().unwrap();
306
307 assert!(string_json.contains(r#""id":"uuid-12345""#));
308 assert!(numeric_json.contains(r#""id":67890"#));
309 }
310
311 #[test]
312 fn test_version_info() {
313 // Test version information is accessible
314 let version_str = version();
315 assert!(!version_str.is_empty());
316 assert_eq!(version_str, VERSION);
317 }
318
319 #[test]
320 fn test_message_trait_consistency() {
321 // Test that all message types use consistent trait implementation
322 let request = JsonRpcRequest::new("test", None, RequestId::new_number(1));
323 let response = JsonRpcResponse::success(json!("ok"), RequestId::new_number(1));
324 let notification = JsonRpcNotification::new("event", None);
325
326 // All should support both regular and pretty JSON
327 assert!(request.to_json().is_ok());
328 assert!(request.to_json_pretty().is_ok());
329
330 assert!(response.to_json().is_ok());
331 assert!(response.to_json_pretty().is_ok());
332
333 assert!(notification.to_json().is_ok());
334 assert!(notification.to_json_pretty().is_ok());
335
336 // All should support bytes deserialization
337 let request_json = request.to_json().unwrap();
338 let request_bytes = request_json.as_bytes();
339 assert!(JsonRpcRequest::from_json_bytes(request_bytes).is_ok());
340 }
341
342 #[test]
343 fn test_json_rpc_compliance() {
344 // Test that all message types maintain JSON-RPC 2.0 compliance
345 let request = JsonRpcRequest::new("ping", None, RequestId::new_number(1));
346 let response = JsonRpcResponse::success(json!("pong"), RequestId::new_number(1));
347 let notification = JsonRpcNotification::new("heartbeat", None);
348
349 let request_json = request.to_json().unwrap();
350 let response_json = response.to_json().unwrap();
351 let notification_json = notification.to_json().unwrap();
352
353 // All must have jsonrpc: "2.0"
354 assert!(request_json.contains(r#""jsonrpc":"2.0""#));
355 assert!(response_json.contains(r#""jsonrpc":"2.0""#));
356 assert!(notification_json.contains(r#""jsonrpc":"2.0""#));
357
358 // Requests and notifications must have method
359 assert!(request_json.contains(r#""method":"ping""#));
360 assert!(notification_json.contains(r#""method":"heartbeat""#));
361
362 // Requests must have id, notifications must not
363 assert!(request_json.contains(r#""id":1"#));
364 assert!(!notification_json.contains("id"));
365
366 // Responses must have result or error, and id
367 assert!(response_json.contains(r#""result":"pong""#));
368 assert!(response_json.contains(r#""id":1"#));
369 assert!(!response_json.contains("error"));
370 }
371}