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}