The smokesignal.events web application
at main 141 lines 4.1 kB view raw
1//! MCP client storage operations. 2//! 3//! Stores dynamically registered MCP clients for OAuth flow. 4 5use chrono::{DateTime, Utc}; 6use serde::{Deserialize, Serialize}; 7use sqlx::FromRow; 8 9use super::{StoragePool, errors::StorageError}; 10 11/// An MCP client registered for OAuth. 12#[derive(Clone, Debug, FromRow, Serialize, Deserialize)] 13pub struct McpClient { 14 /// Unique internal ID 15 pub id: String, 16 /// OAuth client_id (URL format) 17 pub client_id: String, 18 /// OAuth client_secret 19 pub client_secret: String, 20 /// OAuth redirect_uri 21 pub redirect_uri: String, 22 /// Human-readable client name 23 pub client_name: Option<String>, 24 /// DID of the authenticated user (after OAuth completes) 25 pub did: Option<String>, 26 /// DPoP JWK for token binding 27 pub dpop_jwk: String, 28 /// ATProto authorization server issuer 29 pub issuer: Option<String>, 30 /// When the client was registered 31 pub created_at: DateTime<Utc>, 32 /// When the client was last updated 33 pub updated_at: DateTime<Utc>, 34} 35 36/// Create a new MCP client registration. 37pub async fn mcp_client_create( 38 pool: &StoragePool, 39 id: &str, 40 client_id: &str, 41 client_secret: &str, 42 redirect_uri: &str, 43 client_name: Option<&str>, 44 dpop_jwk: &str, 45) -> Result<McpClient, StorageError> { 46 let now = Utc::now(); 47 48 sqlx::query_as::<_, McpClient>( 49 r#" 50 INSERT INTO mcp_clients (id, client_id, client_secret, redirect_uri, client_name, dpop_jwk, created_at, updated_at) 51 VALUES ($1, $2, $3, $4, $5, $6, $7, $7) 52 RETURNING * 53 "#, 54 ) 55 .bind(id) 56 .bind(client_id) 57 .bind(client_secret) 58 .bind(redirect_uri) 59 .bind(client_name) 60 .bind(dpop_jwk) 61 .bind(now) 62 .fetch_one(pool) 63 .await 64 .map_err(StorageError::UnableToExecuteQuery) 65} 66 67/// Get an MCP client by its client_id. 68pub async fn mcp_client_get_by_client_id( 69 pool: &StoragePool, 70 client_id: &str, 71) -> Result<McpClient, StorageError> { 72 sqlx::query_as::<_, McpClient>("SELECT * FROM mcp_clients WHERE client_id = $1") 73 .bind(client_id) 74 .fetch_one(pool) 75 .await 76 .map_err(|err| match err { 77 sqlx::Error::RowNotFound => StorageError::RowNotFound("mcp_client".to_string(), err), 78 other => StorageError::UnableToExecuteQuery(other), 79 }) 80} 81 82/// Get an MCP client by its internal ID. 83pub async fn mcp_client_get_by_id(pool: &StoragePool, id: &str) -> Result<McpClient, StorageError> { 84 sqlx::query_as::<_, McpClient>("SELECT * FROM mcp_clients WHERE id = $1") 85 .bind(id) 86 .fetch_one(pool) 87 .await 88 .map_err(|err| match err { 89 sqlx::Error::RowNotFound => StorageError::RowNotFound("mcp_client".to_string(), err), 90 other => StorageError::UnableToExecuteQuery(other), 91 }) 92} 93 94/// Update an MCP client with authentication info after OAuth completes. 95pub async fn mcp_client_update_auth( 96 pool: &StoragePool, 97 client_id: &str, 98 did: &str, 99 issuer: &str, 100) -> Result<(), StorageError> { 101 let now = Utc::now(); 102 103 sqlx::query( 104 r#" 105 UPDATE mcp_clients 106 SET did = $1, issuer = $2, updated_at = $3 107 WHERE client_id = $4 108 "#, 109 ) 110 .bind(did) 111 .bind(issuer) 112 .bind(now) 113 .bind(client_id) 114 .execute(pool) 115 .await 116 .map_err(StorageError::UnableToExecuteQuery)?; 117 118 Ok(()) 119} 120 121/// Delete an MCP client by its client_id. 122pub async fn mcp_client_delete(pool: &StoragePool, client_id: &str) -> Result<(), StorageError> { 123 sqlx::query("DELETE FROM mcp_clients WHERE client_id = $1") 124 .bind(client_id) 125 .execute(pool) 126 .await 127 .map_err(StorageError::UnableToExecuteQuery)?; 128 129 Ok(()) 130} 131 132/// Delete an MCP client by its internal ID. 133pub async fn mcp_client_delete_by_id(pool: &StoragePool, id: &str) -> Result<(), StorageError> { 134 sqlx::query("DELETE FROM mcp_clients WHERE id = $1") 135 .bind(id) 136 .execute(pool) 137 .await 138 .map_err(StorageError::UnableToExecuteQuery)?; 139 140 Ok(()) 141}