gglib_core/ports/mcp_repository.rs
1//! MCP server repository trait and error types.
2//!
3//! This module defines the repository abstraction for MCP server persistence.
4
5use async_trait::async_trait;
6use thiserror::Error;
7
8use crate::domain::mcp::{McpServer, NewMcpServer};
9
10/// Domain-specific errors for MCP repository operations.
11///
12/// This error type abstracts away storage implementation details and provides
13/// a clean interface for services to handle MCP storage failures.
14#[derive(Debug, Error)]
15pub enum McpRepositoryError {
16 /// The requested MCP server was not found.
17 #[error("MCP server not found: {0}")]
18 NotFound(String),
19
20 /// An MCP server with the same name already exists.
21 #[error("MCP server already exists: {0}")]
22 Conflict(String),
23
24 /// Storage backend error (database, etc.).
25 #[error("Storage error: {0}")]
26 Internal(String),
27}
28
29/// Repository trait for MCP server persistence.
30///
31/// This trait defines the interface for storing and retrieving MCP server
32/// configurations. Implementations handle all persistence details internally.
33///
34/// # Design Rules
35///
36/// - Environment variables are embedded in `McpServer` - no separate env API
37/// - `update()` replaces the entire server including env atomically
38/// - Constraint: unique `name` across all servers
39///
40/// # Example
41///
42/// ```ignore
43/// // Insert a new server
44/// let server = repo.insert(NewMcpServer::new_stdio("my-server", "npx", vec![])).await?;
45///
46/// // Get by ID
47/// let found = repo.get_by_id(server.id).await?;
48///
49/// // List all servers
50/// let all = repo.list().await?;
51/// ```
52#[async_trait]
53pub trait McpServerRepository: Send + Sync {
54 /// Insert a new MCP server.
55 ///
56 /// Returns the server with its assigned ID and timestamps.
57 ///
58 /// # Errors
59 ///
60 /// - `Conflict` if a server with the same name already exists
61 /// - `Internal` for storage errors
62 async fn insert(&self, server: NewMcpServer) -> Result<McpServer, McpRepositoryError>;
63
64 /// Get an MCP server by its database ID.
65 ///
66 /// # Errors
67 ///
68 /// - `NotFound` if no server with the given ID exists
69 /// - `Internal` for storage errors
70 async fn get_by_id(&self, id: i64) -> Result<McpServer, McpRepositoryError>;
71
72 /// Get an MCP server by its unique name.
73 ///
74 /// # Errors
75 ///
76 /// - `NotFound` if no server with the given name exists
77 /// - `Internal` for storage errors
78 async fn get_by_name(&self, name: &str) -> Result<McpServer, McpRepositoryError>;
79
80 /// List all MCP servers.
81 ///
82 /// # Errors
83 ///
84 /// - `Internal` for storage errors
85 async fn list(&self) -> Result<Vec<McpServer>, McpRepositoryError>;
86
87 /// Update an existing MCP server.
88 ///
89 /// This atomically replaces the entire server including environment variables.
90 ///
91 /// # Errors
92 ///
93 /// - `NotFound` if no server with the given ID exists
94 /// - `Conflict` if the new name conflicts with another server
95 /// - `Internal` for storage errors
96 async fn update(&self, server: &McpServer) -> Result<(), McpRepositoryError>;
97
98 /// Delete an MCP server by its database ID.
99 ///
100 /// # Errors
101 ///
102 /// - `NotFound` if no server with the given ID exists
103 /// - `Internal` for storage errors
104 async fn delete(&self, id: i64) -> Result<(), McpRepositoryError>;
105
106 /// Update only the `last_connected_at` timestamp.
107 ///
108 /// This is a narrow, first-class method for updating connection time
109 /// without replacing the entire server.
110 ///
111 /// # Errors
112 ///
113 /// - `NotFound` if no server with the given ID exists
114 /// - `Internal` for storage errors
115 async fn update_last_connected(&self, id: i64) -> Result<(), McpRepositoryError>;
116}