1use std::marker::PhantomData;
31use std::time::{Duration, Instant};
32
33use crate::capability::{
34 ClientCapabilities, ClientInfo, InitializeRequest, InitializeResult, ServerCapabilities,
35 ServerInfo,
36};
37use crate::error::McpError;
38use crate::protocol::RequestId;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct Disconnected;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct Connected;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Initializing;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Ready;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Closing;
59
60#[doc(hidden)]
62#[derive(Debug)]
63pub struct ConnectionInner {
64 pub id: String,
66 pub connected_at: Option<Instant>,
68 pub last_activity: Option<Instant>,
70 pub request_counter: u64,
72 pub client_info: Option<ClientInfo>,
74 pub server_info: Option<ServerInfo>,
76 pub client_capabilities: Option<ClientCapabilities>,
78 pub server_capabilities: Option<ServerCapabilities>,
80}
81
82impl ConnectionInner {
83 fn new() -> Self {
85 Self {
86 id: uuid::Uuid::new_v4().to_string(),
87 connected_at: None,
88 last_activity: None,
89 request_counter: 0,
90 client_info: None,
91 server_info: None,
92 client_capabilities: None,
93 server_capabilities: None,
94 }
95 }
96
97 fn next_request_id(&mut self) -> RequestId {
99 self.request_counter += 1;
100 RequestId::Number(self.request_counter)
101 }
102
103 fn touch(&mut self) {
105 self.last_activity = Some(Instant::now());
106 }
107}
108
109impl Default for ConnectionInner {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115#[derive(Debug)]
120pub struct Connection<S> {
121 inner: ConnectionInner,
122 _state: PhantomData<S>,
123}
124
125impl Connection<Disconnected> {
126 #[must_use]
128 pub fn new() -> Self {
129 Self {
130 inner: ConnectionInner::new(),
131 _state: PhantomData,
132 }
133 }
134
135 #[must_use]
137 pub fn id(&self) -> &str {
138 &self.inner.id
139 }
140
141 #[must_use]
146 pub fn connect(mut self) -> Connection<Connected> {
147 self.inner.connected_at = Some(Instant::now());
148 self.inner.touch();
149 Connection {
150 inner: self.inner,
151 _state: PhantomData,
152 }
153 }
154}
155
156impl Default for Connection<Disconnected> {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl Connection<Connected> {
163 #[must_use]
165 pub fn id(&self) -> &str {
166 &self.inner.id
167 }
168
169 #[must_use]
171 pub const fn connected_at(&self) -> Option<Instant> {
172 self.inner.connected_at
173 }
174
175 #[must_use]
177 pub fn uptime(&self) -> Duration {
178 self.inner
179 .connected_at
180 .map(|t| t.elapsed())
181 .unwrap_or_default()
182 }
183
184 #[must_use]
189 pub fn initialize(
190 mut self,
191 client_info: ClientInfo,
192 client_capabilities: ClientCapabilities,
193 ) -> (Connection<Initializing>, InitializeRequest) {
194 self.inner.client_info = Some(client_info.clone());
195 self.inner.client_capabilities = Some(client_capabilities.clone());
196 self.inner.touch();
197
198 let request = InitializeRequest::new(client_info, client_capabilities);
199
200 (
201 Connection {
202 inner: self.inner,
203 _state: PhantomData,
204 },
205 request,
206 )
207 }
208
209 #[must_use]
211 pub fn disconnect(self) -> Connection<Disconnected> {
212 Connection {
213 inner: ConnectionInner::new(),
214 _state: PhantomData,
215 }
216 }
217}
218
219impl Connection<Initializing> {
220 #[must_use]
222 pub fn id(&self) -> &str {
223 &self.inner.id
224 }
225
226 #[must_use]
228 pub const fn client_info(&self) -> Option<&ClientInfo> {
229 self.inner.client_info.as_ref()
230 }
231
232 #[must_use]
234 pub const fn client_capabilities(&self) -> Option<&ClientCapabilities> {
235 self.inner.client_capabilities.as_ref()
236 }
237
238 #[must_use]
243 pub fn complete(
244 mut self,
245 server_info: ServerInfo,
246 server_capabilities: ServerCapabilities,
247 ) -> Connection<Ready> {
248 self.inner.server_info = Some(server_info);
249 self.inner.server_capabilities = Some(server_capabilities);
250 self.inner.touch();
251
252 Connection {
253 inner: self.inner,
254 _state: PhantomData,
255 }
256 }
257
258 #[must_use]
260 pub fn abort(self) -> Connection<Disconnected> {
261 Connection {
262 inner: ConnectionInner::new(),
263 _state: PhantomData,
264 }
265 }
266}
267
268impl Connection<Ready> {
269 #[must_use]
271 pub fn id(&self) -> &str {
272 &self.inner.id
273 }
274
275 #[must_use]
277 pub const fn connected_at(&self) -> Option<Instant> {
278 self.inner.connected_at
279 }
280
281 #[must_use]
283 pub fn uptime(&self) -> Duration {
284 self.inner
285 .connected_at
286 .map(|t| t.elapsed())
287 .unwrap_or_default()
288 }
289
290 #[must_use]
292 pub const fn last_activity(&self) -> Option<Instant> {
293 self.inner.last_activity
294 }
295
296 #[must_use]
304 pub fn client_info(&self) -> &ClientInfo {
305 self.inner
306 .client_info
307 .as_ref()
308 .expect("client_info should be set in Ready state")
309 }
310
311 #[must_use]
315 pub const fn try_client_info(&self) -> Option<&ClientInfo> {
316 self.inner.client_info.as_ref()
317 }
318
319 #[must_use]
327 pub fn server_info(&self) -> &ServerInfo {
328 self.inner
329 .server_info
330 .as_ref()
331 .expect("server_info should be set in Ready state")
332 }
333
334 #[must_use]
338 pub const fn try_server_info(&self) -> Option<&ServerInfo> {
339 self.inner.server_info.as_ref()
340 }
341
342 #[must_use]
350 pub fn client_capabilities(&self) -> &ClientCapabilities {
351 self.inner
352 .client_capabilities
353 .as_ref()
354 .expect("client_capabilities should be set in Ready state")
355 }
356
357 #[must_use]
361 pub const fn try_client_capabilities(&self) -> Option<&ClientCapabilities> {
362 self.inner.client_capabilities.as_ref()
363 }
364
365 #[must_use]
373 pub fn server_capabilities(&self) -> &ServerCapabilities {
374 self.inner
375 .server_capabilities
376 .as_ref()
377 .expect("server_capabilities should be set in Ready state")
378 }
379
380 #[must_use]
384 pub const fn try_server_capabilities(&self) -> Option<&ServerCapabilities> {
385 self.inner.server_capabilities.as_ref()
386 }
387
388 pub fn next_request_id(&mut self) -> RequestId {
390 self.inner.next_request_id()
391 }
392
393 pub fn touch(&mut self) {
395 self.inner.touch();
396 }
397
398 #[must_use]
400 pub fn is_idle(&self, timeout: Duration) -> bool {
401 self.inner
402 .last_activity
403 .is_some_and(|t| t.elapsed() > timeout)
404 }
405
406 #[must_use]
408 pub fn shutdown(self) -> Connection<Closing> {
409 Connection {
410 inner: self.inner,
411 _state: PhantomData,
412 }
413 }
414}
415
416impl Connection<Closing> {
417 #[must_use]
419 pub fn id(&self) -> &str {
420 &self.inner.id
421 }
422
423 #[must_use]
425 pub fn close(self) -> Connection<Disconnected> {
426 Connection {
427 inner: ConnectionInner::new(),
428 _state: PhantomData,
429 }
430 }
431}
432
433pub struct InitializeResultBuilder {
435 server_info: ServerInfo,
436 capabilities: ServerCapabilities,
437 instructions: Option<String>,
438}
439
440impl InitializeResultBuilder {
441 #[must_use]
443 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
444 Self {
445 server_info: ServerInfo::new(name, version),
446 capabilities: ServerCapabilities::new(),
447 instructions: None,
448 }
449 }
450
451 #[must_use]
453 pub fn capabilities(mut self, caps: ServerCapabilities) -> Self {
454 self.capabilities = caps;
455 self
456 }
457
458 #[must_use]
460 pub fn with_tools(mut self) -> Self {
461 self.capabilities = self.capabilities.with_tools();
462 self
463 }
464
465 #[must_use]
467 pub fn with_resources(mut self) -> Self {
468 self.capabilities = self.capabilities.with_resources();
469 self
470 }
471
472 #[must_use]
474 pub fn with_prompts(mut self) -> Self {
475 self.capabilities = self.capabilities.with_prompts();
476 self
477 }
478
479 #[must_use]
481 pub fn with_tasks(mut self) -> Self {
482 self.capabilities = self.capabilities.with_tasks();
483 self
484 }
485
486 #[must_use]
488 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
489 self.instructions = Some(instructions.into());
490 self
491 }
492
493 #[must_use]
495 pub fn build(self) -> InitializeResult {
496 let mut result = InitializeResult::new(self.server_info, self.capabilities);
497 if let Some(instructions) = self.instructions {
498 result = result.instructions(instructions);
499 }
500 result
501 }
502}
503
504pub const fn validate_initialization(
506 _client_caps: &ClientCapabilities,
507 _server_caps: &ServerCapabilities,
508) -> Result<(), McpError> {
509 Ok(())
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 fn test_connection_lifecycle() {
520 let conn: Connection<Disconnected> = Connection::new();
522 assert!(!conn.id().is_empty());
523
524 let conn: Connection<Connected> = conn.connect();
526 assert!(conn.connected_at().is_some());
527
528 let client = ClientInfo::new("test", "1.0.0");
530 let caps = ClientCapabilities::new();
531 let (conn, _request): (Connection<Initializing>, _) = conn.initialize(client, caps);
532 assert!(conn.client_info().is_some());
533
534 let server = ServerInfo::new("server", "1.0.0");
536 let server_caps = ServerCapabilities::new().with_tools();
537 let mut conn: Connection<Ready> = conn.complete(server, server_caps);
538 assert!(conn.server_capabilities().has_tools());
539
540 let id1 = conn.next_request_id();
542 let id2 = conn.next_request_id();
543 assert_ne!(id1, id2);
544
545 let conn: Connection<Closing> = conn.shutdown();
547 let _conn: Connection<Disconnected> = conn.close();
548 }
549
550 #[test]
551 fn test_uptime() {
552 let conn = Connection::new().connect();
553 std::thread::sleep(std::time::Duration::from_millis(10));
554 assert!(conn.uptime() >= std::time::Duration::from_millis(10));
555 }
556
557 #[test]
558 fn test_idle_detection() {
559 let client = ClientInfo::new("test", "1.0.0");
560 let server = ServerInfo::new("server", "1.0.0");
561
562 let (conn, _) = Connection::new()
563 .connect()
564 .initialize(client, ClientCapabilities::new());
565
566 let conn = conn.complete(server, ServerCapabilities::new());
567
568 assert!(!conn.is_idle(Duration::from_secs(1)));
570 }
571
572 #[test]
573 fn test_initialize_result_builder() {
574 let result = InitializeResultBuilder::new("my-server", "1.0.0")
575 .with_tools()
576 .with_resources()
577 .instructions("Use this server to access tools and resources")
578 .build();
579
580 assert_eq!(result.server_info.name, "my-server");
581 assert!(result.capabilities.has_tools());
582 assert!(result.capabilities.has_resources());
583 assert!(result.instructions.is_some());
584 }
585
586 #[test]
587 fn test_abort_initialization() {
588 let client = ClientInfo::new("test", "1.0.0");
589 let (conn, _) = Connection::new()
590 .connect()
591 .initialize(client, ClientCapabilities::new());
592
593 let _conn: Connection<Disconnected> = conn.abort();
595 }
596
597 #[test]
598 fn test_disconnect_from_connected() {
599 let conn = Connection::new().connect();
600 let _conn: Connection<Disconnected> = conn.disconnect();
601 }
602
603 #[test]
604 fn test_fallible_accessors() -> Result<(), Box<dyn std::error::Error>> {
605 let client = ClientInfo::new("test-client", "1.0.0");
606 let server = ServerInfo::new("test-server", "2.0.0");
607 let client_caps = ClientCapabilities::new();
608 let server_caps = ServerCapabilities::new().with_tools();
609
610 let (conn, _) = Connection::new().connect().initialize(client, client_caps);
611
612 let conn = conn.complete(server, server_caps);
613
614 assert!(conn.try_client_info().is_some());
616 assert!(conn.try_server_info().is_some());
617 assert!(conn.try_client_capabilities().is_some());
618 assert!(conn.try_server_capabilities().is_some());
619
620 assert_eq!(
622 conn.try_client_info().ok_or("Expected client info")?.name,
623 "test-client"
624 );
625 assert_eq!(
626 conn.try_server_info().ok_or("Expected server info")?.name,
627 "test-server"
628 );
629 assert!(
630 conn.try_server_capabilities()
631 .ok_or("Expected server capabilities")?
632 .has_tools()
633 );
634 Ok(())
635 }
636}