feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
use rusqlite::{Connection, OptionalExtension};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
use crate::integrations::auth::{decrypt_token, encrypt_token};
|
|
|
|
|
use crate::mcp::models::{
|
|
|
|
|
CreateMcpServerRequest, McpResource, McpServer, McpTool, UpdateMcpServerRequest,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-23 21:48:26 +00:00
|
|
|
pub fn create_server(conn: &Connection, req: &CreateMcpServerRequest) -> Result<McpServer, String> {
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
let id = Uuid::now_v7().to_string();
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
|
|
|
|
|
let encrypted_auth = match &req.auth_value {
|
|
|
|
|
Some(v) if !v.is_empty() => Some(encrypt_token(v)?),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-01 13:22:29 +00:00
|
|
|
let encrypted_env = match &req.env_config {
|
|
|
|
|
Some(env_json) if !env_json.trim().is_empty() => Some(encrypt_token(env_json)?),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO mcp_servers
|
2026-06-01 13:22:29 +00:00
|
|
|
(id, name, url, transport_type, transport_config, auth_type, auth_value, enabled, env_config, created_at, updated_at)
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?10)",
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
rusqlite::params![
|
|
|
|
|
id,
|
|
|
|
|
req.name,
|
|
|
|
|
req.url,
|
|
|
|
|
req.transport_type,
|
|
|
|
|
req.transport_config,
|
|
|
|
|
req.auth_type,
|
|
|
|
|
encrypted_auth,
|
|
|
|
|
req.enabled as i32,
|
2026-06-01 13:22:29 +00:00
|
|
|
encrypted_env,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
now,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
get_server(conn, &id)?.ok_or_else(|| "Server not found after insert".to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_server(conn: &Connection, id: &str) -> Result<Option<McpServer>, String> {
|
|
|
|
|
let row = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT id, name, url, transport_type, transport_config, auth_type, auth_value,
|
|
|
|
|
enabled, last_discovered_at, discovery_status, discovery_error,
|
2026-06-01 13:22:29 +00:00
|
|
|
created_at, updated_at, env_config
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
FROM mcp_servers WHERE id = ?1",
|
|
|
|
|
[id],
|
|
|
|
|
|row| {
|
|
|
|
|
Ok(McpServer {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
name: row.get(1)?,
|
|
|
|
|
url: row.get(2)?,
|
|
|
|
|
transport_type: row.get(3)?,
|
|
|
|
|
transport_config: row.get(4)?,
|
|
|
|
|
auth_type: row.get(5)?,
|
|
|
|
|
auth_value: row.get(6)?,
|
|
|
|
|
enabled: row.get::<_, i32>(7)? != 0,
|
|
|
|
|
last_discovered_at: row.get(8)?,
|
|
|
|
|
discovery_status: row.get(9)?,
|
|
|
|
|
discovery_error: row.get(10)?,
|
|
|
|
|
created_at: row.get(11)?,
|
|
|
|
|
updated_at: row.get(12)?,
|
2026-06-01 13:22:29 +00:00
|
|
|
env_config: row.get(13)?,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
Ok(row)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn list_servers(conn: &Connection) -> Result<Vec<McpServer>, String> {
|
|
|
|
|
let mut stmt = conn
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, name, url, transport_type, transport_config, auth_type, auth_value,
|
|
|
|
|
enabled, last_discovered_at, discovery_status, discovery_error,
|
2026-06-01 13:22:29 +00:00
|
|
|
created_at, updated_at, env_config
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
FROM mcp_servers ORDER BY created_at ASC",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
let rows = stmt
|
|
|
|
|
.query_map([], |row| {
|
|
|
|
|
Ok(McpServer {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
name: row.get(1)?,
|
|
|
|
|
url: row.get(2)?,
|
|
|
|
|
transport_type: row.get(3)?,
|
|
|
|
|
transport_config: row.get(4)?,
|
|
|
|
|
auth_type: row.get(5)?,
|
|
|
|
|
auth_value: row.get(6)?,
|
|
|
|
|
enabled: row.get::<_, i32>(7)? != 0,
|
|
|
|
|
last_discovered_at: row.get(8)?,
|
|
|
|
|
discovery_status: row.get(9)?,
|
|
|
|
|
discovery_error: row.get(10)?,
|
|
|
|
|
created_at: row.get(11)?,
|
|
|
|
|
updated_at: row.get(12)?,
|
2026-06-01 13:22:29 +00:00
|
|
|
env_config: row.get(13)?,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.collect::<Result<Vec<_>, _>>()
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(rows)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_server(
|
|
|
|
|
conn: &Connection,
|
|
|
|
|
id: &str,
|
|
|
|
|
req: &UpdateMcpServerRequest,
|
|
|
|
|
) -> Result<McpServer, String> {
|
2026-05-23 21:48:26 +00:00
|
|
|
let existing = get_server(conn, id)?.ok_or_else(|| format!("Server {id} not found"))?;
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
|
|
|
|
|
let new_encrypted_auth = match &req.auth_value {
|
|
|
|
|
Some(v) if !v.is_empty() => Some(encrypt_token(v)?),
|
|
|
|
|
Some(_) => None,
|
|
|
|
|
None => existing.auth_value.clone(),
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-01 13:22:29 +00:00
|
|
|
let new_encrypted_env = match &req.env_config {
|
|
|
|
|
Some(env_json) if !env_json.trim().is_empty() => Some(encrypt_token(env_json)?),
|
2026-06-01 16:58:52 +00:00
|
|
|
Some(_) => None, // Empty string = clear env_config
|
|
|
|
|
None => existing.env_config.clone(), // No update requested
|
2026-06-01 13:22:29 +00:00
|
|
|
};
|
|
|
|
|
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE mcp_servers SET
|
|
|
|
|
name = ?1, url = ?2, transport_type = ?3, transport_config = ?4,
|
2026-06-01 13:22:29 +00:00
|
|
|
auth_type = ?5, auth_value = ?6, enabled = ?7, env_config = ?8, updated_at = ?9
|
|
|
|
|
WHERE id = ?10",
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
rusqlite::params![
|
|
|
|
|
req.name.as_deref().unwrap_or(&existing.name),
|
|
|
|
|
req.url.as_deref().unwrap_or(&existing.url),
|
2026-05-23 21:48:26 +00:00
|
|
|
req.transport_type
|
|
|
|
|
.as_deref()
|
|
|
|
|
.unwrap_or(&existing.transport_type),
|
|
|
|
|
req.transport_config
|
|
|
|
|
.as_deref()
|
|
|
|
|
.unwrap_or(&existing.transport_config),
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
req.auth_type.as_deref().unwrap_or(&existing.auth_type),
|
|
|
|
|
new_encrypted_auth,
|
2026-05-23 21:48:26 +00:00
|
|
|
req.enabled
|
|
|
|
|
.map(|b| b as i32)
|
|
|
|
|
.unwrap_or(existing.enabled as i32),
|
2026-06-01 13:22:29 +00:00
|
|
|
new_encrypted_env,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
now,
|
|
|
|
|
id,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
get_server(conn, id)?.ok_or_else(|| "Server not found after update".to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn delete_server(conn: &Connection, id: &str) -> Result<(), String> {
|
|
|
|
|
conn.execute("DELETE FROM mcp_servers WHERE id = ?1", [id])
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_server(conn: &Connection, id: &str, enabled: bool) -> Result<(), String> {
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE mcp_servers SET enabled = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![enabled as i32, now, id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_discovery_status(
|
|
|
|
|
conn: &Connection,
|
|
|
|
|
id: &str,
|
|
|
|
|
status: &str,
|
|
|
|
|
error: Option<&str>,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE mcp_servers SET discovery_status = ?1, discovery_error = ?2,
|
|
|
|
|
last_discovered_at = ?3, updated_at = ?3 WHERE id = ?4",
|
|
|
|
|
rusqlite::params![status, error, now, id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn replace_tools(conn: &Connection, server_id: &str, tools: &[McpTool]) -> Result<(), String> {
|
|
|
|
|
conn.execute("DELETE FROM mcp_tools WHERE server_id = ?1", [server_id])
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
for tool in tools {
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO mcp_tools (id, server_id, name, tool_key, description, parameters)
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
tool.id,
|
|
|
|
|
tool.server_id,
|
|
|
|
|
tool.name,
|
|
|
|
|
tool.tool_key,
|
|
|
|
|
tool.description,
|
|
|
|
|
tool.parameters,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn replace_resources(
|
|
|
|
|
conn: &Connection,
|
|
|
|
|
server_id: &str,
|
|
|
|
|
resources: &[McpResource],
|
|
|
|
|
) -> Result<(), String> {
|
2026-05-23 21:48:26 +00:00
|
|
|
conn.execute(
|
|
|
|
|
"DELETE FROM mcp_resources WHERE server_id = ?1",
|
|
|
|
|
[server_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
|
|
|
|
|
for res in resources {
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO mcp_resources (id, server_id, uri, name, description)
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
|
|
|
rusqlite::params![res.id, res.server_id, res.uri, res.name, res.description],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_enabled_tools(conn: &Connection) -> Result<Vec<(McpTool, String)>, String> {
|
|
|
|
|
let mut stmt = conn
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT t.id, t.server_id, t.name, t.tool_key, t.description, t.parameters, s.url
|
|
|
|
|
FROM mcp_tools t
|
|
|
|
|
JOIN mcp_servers s ON t.server_id = s.id
|
|
|
|
|
WHERE s.enabled = 1 AND s.discovery_status = 'connected'",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
let rows = stmt
|
|
|
|
|
.query_map([], |row| {
|
|
|
|
|
Ok((
|
|
|
|
|
McpTool {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
server_id: row.get(1)?,
|
|
|
|
|
name: row.get(2)?,
|
|
|
|
|
tool_key: row.get(3)?,
|
|
|
|
|
description: row.get(4)?,
|
|
|
|
|
parameters: row.get(5)?,
|
|
|
|
|
},
|
|
|
|
|
row.get::<_, String>(6)?,
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.collect::<Result<Vec<_>, _>>()
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(rows)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_tool_by_key(conn: &Connection, tool_key: &str) -> Result<Option<McpTool>, String> {
|
|
|
|
|
conn.query_row(
|
|
|
|
|
"SELECT id, server_id, name, tool_key, description, parameters
|
|
|
|
|
FROM mcp_tools WHERE tool_key = ?1",
|
|
|
|
|
[tool_key],
|
|
|
|
|
|row| {
|
|
|
|
|
Ok(McpTool {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
server_id: row.get(1)?,
|
|
|
|
|
name: row.get(2)?,
|
|
|
|
|
tool_key: row.get(3)?,
|
|
|
|
|
description: row.get(4)?,
|
|
|
|
|
parameters: row.get(5)?,
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-23 21:48:26 +00:00
|
|
|
pub fn get_server_auth_value(conn: &Connection, server_id: &str) -> Result<Option<String>, String> {
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
let encrypted: Option<String> = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT auth_value FROM mcp_servers WHERE id = ?1",
|
|
|
|
|
[server_id],
|
|
|
|
|
|row| row.get(0),
|
|
|
|
|
)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.flatten();
|
|
|
|
|
|
|
|
|
|
match encrypted {
|
|
|
|
|
Some(enc) => Ok(Some(decrypt_token(&enc)?)),
|
|
|
|
|
None => Ok(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_tool_count(conn: &Connection, server_id: &str) -> Result<usize, String> {
|
|
|
|
|
conn.query_row(
|
|
|
|
|
"SELECT COUNT(*) FROM mcp_tools WHERE server_id = ?1",
|
|
|
|
|
[server_id],
|
|
|
|
|
|r| r.get::<_, i64>(0),
|
|
|
|
|
)
|
|
|
|
|
.map(|n| n as usize)
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_resource_count(conn: &Connection, server_id: &str) -> Result<usize, String> {
|
|
|
|
|
conn.query_row(
|
|
|
|
|
"SELECT COUNT(*) FROM mcp_resources WHERE server_id = ?1",
|
|
|
|
|
[server_id],
|
|
|
|
|
|r| r.get::<_, i64>(0),
|
|
|
|
|
)
|
|
|
|
|
.map(|n| n as usize)
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 13:22:29 +00:00
|
|
|
/// Decrypt and parse env_config from database, returning a HashMap.
|
|
|
|
|
/// Returns None if env_config is NULL, or an error if decryption/parsing fails.
|
|
|
|
|
pub fn get_server_env_config(
|
|
|
|
|
conn: &Connection,
|
|
|
|
|
server_id: &str,
|
|
|
|
|
) -> Result<Option<std::collections::HashMap<String, String>>, String> {
|
|
|
|
|
let encrypted: Option<String> = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT env_config FROM mcp_servers WHERE id = ?1",
|
|
|
|
|
[server_id],
|
|
|
|
|
|row| row.get(0),
|
|
|
|
|
)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.flatten();
|
|
|
|
|
|
|
|
|
|
match encrypted {
|
|
|
|
|
Some(enc) => {
|
|
|
|
|
let decrypted = decrypt_token(&enc)?;
|
2026-06-01 16:58:52 +00:00
|
|
|
let parsed: std::collections::HashMap<String, String> =
|
|
|
|
|
serde_json::from_str(&decrypted)
|
|
|
|
|
.map_err(|e| format!("Failed to parse env_config JSON: {e}"))?;
|
2026-06-01 13:22:29 +00:00
|
|
|
Ok(Some(parsed))
|
|
|
|
|
}
|
|
|
|
|
None => Ok(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::db::migrations::run_migrations;
|
|
|
|
|
|
|
|
|
|
fn setup() -> Connection {
|
|
|
|
|
let conn = Connection::open_in_memory().unwrap();
|
|
|
|
|
run_migrations(&conn).unwrap();
|
|
|
|
|
conn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn make_req(name: &str) -> CreateMcpServerRequest {
|
|
|
|
|
CreateMcpServerRequest {
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
url: "http://localhost:8080/mcp".to_string(),
|
|
|
|
|
transport_type: "http".to_string(),
|
|
|
|
|
transport_config: "{}".to_string(),
|
|
|
|
|
auth_type: "none".to_string(),
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: true,
|
2026-06-01 13:22:29 +00:00
|
|
|
env_config: None,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_mcp_server_crud() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
// Create
|
|
|
|
|
let server = create_server(&conn, &make_req("Test Server")).unwrap();
|
|
|
|
|
assert_eq!(server.name, "Test Server");
|
|
|
|
|
assert_eq!(server.transport_type, "http");
|
|
|
|
|
assert_eq!(server.discovery_status, "pending");
|
|
|
|
|
assert!(server.enabled);
|
|
|
|
|
|
|
|
|
|
// Read
|
|
|
|
|
let fetched = get_server(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(fetched.id, server.id);
|
|
|
|
|
|
|
|
|
|
// List
|
|
|
|
|
let all = list_servers(&conn).unwrap();
|
|
|
|
|
assert_eq!(all.len(), 1);
|
|
|
|
|
|
|
|
|
|
// Update
|
|
|
|
|
let updated = update_server(
|
|
|
|
|
&conn,
|
|
|
|
|
&server.id,
|
|
|
|
|
&UpdateMcpServerRequest {
|
|
|
|
|
name: Some("Renamed".to_string()),
|
|
|
|
|
url: None,
|
|
|
|
|
transport_type: None,
|
|
|
|
|
transport_config: None,
|
|
|
|
|
auth_type: None,
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: None,
|
2026-06-01 13:22:29 +00:00
|
|
|
env_config: None,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(updated.name, "Renamed");
|
|
|
|
|
|
|
|
|
|
// Delete
|
|
|
|
|
delete_server(&conn, &server.id).unwrap();
|
|
|
|
|
assert!(get_server(&conn, &server.id).unwrap().is_none());
|
|
|
|
|
assert!(list_servers(&conn).unwrap().is_empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_auth_value_encrypted_at_rest() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let req = CreateMcpServerRequest {
|
|
|
|
|
name: "Secured".to_string(),
|
|
|
|
|
url: "http://localhost/mcp".to_string(),
|
|
|
|
|
transport_type: "http".to_string(),
|
|
|
|
|
transport_config: "{}".to_string(),
|
|
|
|
|
auth_type: "bearer".to_string(),
|
|
|
|
|
auth_value: Some("super-secret-token".to_string()),
|
|
|
|
|
enabled: true,
|
2026-06-01 13:22:29 +00:00
|
|
|
env_config: None,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
};
|
|
|
|
|
let server = create_server(&conn, &req).unwrap();
|
|
|
|
|
|
|
|
|
|
// Raw DB value must NOT equal the plaintext token
|
|
|
|
|
let raw: Option<String> = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT auth_value FROM mcp_servers WHERE id = ?1",
|
|
|
|
|
[&server.id],
|
|
|
|
|
|r| r.get(0),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let raw = raw.unwrap();
|
2026-05-23 21:48:26 +00:00
|
|
|
assert_ne!(
|
|
|
|
|
raw, "super-secret-token",
|
|
|
|
|
"auth_value must be encrypted in DB"
|
|
|
|
|
);
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
|
|
|
|
|
// Decrypted value must match original
|
|
|
|
|
let decrypted = get_server_auth_value(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(decrypted, "super-secret-token");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_disabled_server_excluded_from_tools() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let mut req = make_req("Disabled Server");
|
|
|
|
|
req.enabled = false;
|
|
|
|
|
let server = create_server(&conn, &req).unwrap();
|
|
|
|
|
|
|
|
|
|
// Mark connected and add a tool
|
|
|
|
|
update_discovery_status(&conn, &server.id, "connected", None).unwrap();
|
|
|
|
|
let tool = McpTool {
|
|
|
|
|
id: Uuid::now_v7().to_string(),
|
|
|
|
|
server_id: server.id.clone(),
|
|
|
|
|
name: "echo".to_string(),
|
|
|
|
|
tool_key: "mcp_disabled_server_echo".to_string(),
|
|
|
|
|
description: None,
|
|
|
|
|
parameters: "{}".to_string(),
|
|
|
|
|
};
|
|
|
|
|
replace_tools(&conn, &server.id, &[tool]).unwrap();
|
|
|
|
|
|
|
|
|
|
let tools = get_enabled_tools(&conn).unwrap();
|
|
|
|
|
assert!(tools.is_empty(), "disabled server tools should be excluded");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_update_discovery_status() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let server = create_server(&conn, &make_req("Status Test")).unwrap();
|
|
|
|
|
assert_eq!(server.discovery_status, "pending");
|
|
|
|
|
|
|
|
|
|
update_discovery_status(&conn, &server.id, "connected", None).unwrap();
|
|
|
|
|
let updated = get_server(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(updated.discovery_status, "connected");
|
|
|
|
|
assert!(updated.last_discovered_at.is_some());
|
|
|
|
|
|
|
|
|
|
update_discovery_status(&conn, &server.id, "error", Some("connection refused")).unwrap();
|
|
|
|
|
let errored = get_server(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(errored.discovery_status, "error");
|
2026-05-23 21:48:26 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
errored.discovery_error.as_deref(),
|
|
|
|
|
Some("connection refused")
|
|
|
|
|
);
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_cascade_delete_clears_tools() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
conn.execute("PRAGMA foreign_keys = ON", []).unwrap();
|
|
|
|
|
|
|
|
|
|
let server = create_server(&conn, &make_req("Cascade Test")).unwrap();
|
|
|
|
|
let tool = McpTool {
|
|
|
|
|
id: Uuid::now_v7().to_string(),
|
|
|
|
|
server_id: server.id.clone(),
|
|
|
|
|
name: "ping".to_string(),
|
|
|
|
|
tool_key: "mcp_cascade_test_ping".to_string(),
|
|
|
|
|
description: None,
|
|
|
|
|
parameters: "{}".to_string(),
|
|
|
|
|
};
|
|
|
|
|
replace_tools(&conn, &server.id, &[tool]).unwrap();
|
|
|
|
|
|
|
|
|
|
let count = get_tool_count(&conn, &server.id).unwrap();
|
|
|
|
|
assert_eq!(count, 1);
|
|
|
|
|
|
|
|
|
|
delete_server(&conn, &server.id).unwrap();
|
|
|
|
|
|
|
|
|
|
// After delete, count should be 0 (cascade)
|
|
|
|
|
let count: i64 = conn
|
|
|
|
|
.query_row("SELECT COUNT(*) FROM mcp_tools", [], |r| r.get(0))
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(count, 0, "cascade delete should clear mcp_tools");
|
|
|
|
|
}
|
2026-06-01 13:22:29 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_env_config_encrypted_at_rest() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let req = CreateMcpServerRequest {
|
|
|
|
|
name: "Env Test".to_string(),
|
|
|
|
|
url: "".to_string(),
|
|
|
|
|
transport_type: "stdio".to_string(),
|
|
|
|
|
transport_config: r#"{"command":"/usr/bin/test","args":[]}"#.to_string(),
|
|
|
|
|
auth_type: "none".to_string(),
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: true,
|
|
|
|
|
env_config: Some(r#"{"API_KEY":"secret123","DEBUG":"1"}"#.to_string()),
|
|
|
|
|
};
|
|
|
|
|
let server = create_server(&conn, &req).unwrap();
|
|
|
|
|
|
|
|
|
|
// Raw DB value must be encrypted (not equal to plaintext)
|
|
|
|
|
let raw: Option<String> = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT env_config FROM mcp_servers WHERE id = ?1",
|
|
|
|
|
[&server.id],
|
|
|
|
|
|r| r.get(0),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let raw = raw.unwrap();
|
|
|
|
|
assert_ne!(
|
2026-06-01 16:58:52 +00:00
|
|
|
raw, r#"{"API_KEY":"secret123","DEBUG":"1"}"#,
|
2026-06-01 13:22:29 +00:00
|
|
|
"env_config should be encrypted at rest"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Decrypted value must match original
|
|
|
|
|
let env_map = get_server_env_config(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(env_map.get("API_KEY").unwrap(), "secret123");
|
|
|
|
|
assert_eq!(env_map.get("DEBUG").unwrap(), "1");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_update_env_config() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let server = create_server(&conn, &make_req("Env Update")).unwrap();
|
|
|
|
|
assert!(server.env_config.is_none());
|
|
|
|
|
|
|
|
|
|
let updated = update_server(
|
|
|
|
|
&conn,
|
|
|
|
|
&server.id,
|
|
|
|
|
&UpdateMcpServerRequest {
|
|
|
|
|
name: None,
|
|
|
|
|
url: None,
|
|
|
|
|
transport_type: None,
|
|
|
|
|
transport_config: None,
|
|
|
|
|
auth_type: None,
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: None,
|
|
|
|
|
env_config: Some(r#"{"NEW_VAR":"value"}"#.to_string()),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(updated.env_config.is_some());
|
|
|
|
|
let env_map = get_server_env_config(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(env_map.get("NEW_VAR").unwrap(), "value");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_clear_env_config_with_empty_string() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let mut req = make_req("Clear Env");
|
|
|
|
|
req.env_config = Some(r#"{"KEY":"val"}"#.to_string());
|
|
|
|
|
let server = create_server(&conn, &req).unwrap();
|
|
|
|
|
assert!(server.env_config.is_some());
|
|
|
|
|
|
|
|
|
|
let updated = update_server(
|
|
|
|
|
&conn,
|
|
|
|
|
&server.id,
|
|
|
|
|
&UpdateMcpServerRequest {
|
|
|
|
|
name: None,
|
|
|
|
|
url: None,
|
|
|
|
|
transport_type: None,
|
|
|
|
|
transport_config: None,
|
|
|
|
|
auth_type: None,
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: None,
|
|
|
|
|
env_config: Some("".to_string()), // Clear
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(updated.env_config.is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_env_config_none_preserves_existing() {
|
|
|
|
|
let conn = setup();
|
|
|
|
|
|
|
|
|
|
let mut req = make_req("Preserve Env");
|
|
|
|
|
req.env_config = Some(r#"{"ORIGINAL":"value"}"#.to_string());
|
|
|
|
|
let server = create_server(&conn, &req).unwrap();
|
|
|
|
|
|
|
|
|
|
// Update without touching env_config
|
|
|
|
|
let updated = update_server(
|
|
|
|
|
&conn,
|
|
|
|
|
&server.id,
|
|
|
|
|
&UpdateMcpServerRequest {
|
|
|
|
|
name: Some("New Name".to_string()),
|
|
|
|
|
url: None,
|
|
|
|
|
transport_type: None,
|
|
|
|
|
transport_config: None,
|
|
|
|
|
auth_type: None,
|
|
|
|
|
auth_value: None,
|
|
|
|
|
enabled: None,
|
|
|
|
|
env_config: None, // Don't update
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// env_config should still be there
|
|
|
|
|
assert!(updated.env_config.is_some());
|
|
|
|
|
let env_map = get_server_env_config(&conn, &server.id).unwrap().unwrap();
|
|
|
|
|
assert_eq!(env_map.get("ORIGINAL").unwrap(), "value");
|
|
|
|
|
}
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
}
|