The smokesignal.events web application
1//! MCP configuration storage operations.
2//!
3//! Stores per-user MCP configuration settings.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use sqlx::FromRow;
8
9use super::{StoragePool, errors::StorageError};
10
11/// MCP configuration for a user.
12#[derive(Clone, Debug, FromRow, Serialize, Deserialize)]
13pub struct McpConfiguration {
14 /// User's DID
15 pub did: String,
16 /// Allow dangerous operations (delete, apply_writes, etc.)
17 pub allow_dangerous: bool,
18 /// When the configuration was last updated
19 pub updated_at: DateTime<Utc>,
20}
21
22/// Get MCP configuration for a user.
23/// Returns None if no configuration exists (default settings apply).
24pub async fn mcp_config_get(
25 pool: &StoragePool,
26 did: &str,
27) -> Result<Option<McpConfiguration>, StorageError> {
28 sqlx::query_as::<_, McpConfiguration>("SELECT * FROM mcp_configuration WHERE did = $1")
29 .bind(did)
30 .fetch_optional(pool)
31 .await
32 .map_err(StorageError::UnableToExecuteQuery)
33}
34
35/// Upsert MCP configuration for a user.
36pub async fn mcp_config_upsert(
37 pool: &StoragePool,
38 did: &str,
39 allow_dangerous: bool,
40) -> Result<McpConfiguration, StorageError> {
41 let now = Utc::now();
42
43 sqlx::query_as::<_, McpConfiguration>(
44 r#"
45 INSERT INTO mcp_configuration (did, allow_dangerous, updated_at)
46 VALUES ($1, $2, $3)
47 ON CONFLICT (did)
48 DO UPDATE SET allow_dangerous = $2, updated_at = $3
49 RETURNING *
50 "#,
51 )
52 .bind(did)
53 .bind(allow_dangerous)
54 .bind(now)
55 .fetch_one(pool)
56 .await
57 .map_err(StorageError::UnableToExecuteQuery)
58}
59
60/// Check if dangerous operations are allowed for a user.
61/// Returns false if no configuration exists (safe default).
62pub async fn mcp_config_is_dangerous_allowed(
63 pool: &StoragePool,
64 did: &str,
65) -> Result<bool, StorageError> {
66 let config = mcp_config_get(pool, did).await?;
67 Ok(config.map(|c| c.allow_dangerous).unwrap_or(false))
68}