feat: add embedded MCP server and Makefile

Add an MCP (Model Context Protocol) server that starts on 127.0.0.1:9427
at app launch, sharing connection pools with the Tauri IPC layer. This
lets Claude (or any MCP client) query PostgreSQL through Tusk's existing
connections.

MCP tools: list_connections, execute_query, list_schemas, list_tables,
describe_table.

Also add a Makefile with targets for dev, build (cross-platform),
install/uninstall, lint, and formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 13:24:25 +03:00
parent ded35d8c40
commit 32486b0524
10 changed files with 756 additions and 71 deletions

View File

@@ -4,6 +4,7 @@ use crate::state::AppState;
use sqlx::PgPool;
use sqlx::Row;
use std::fs;
use std::sync::Arc;
use tauri::{AppHandle, Manager, State};
fn get_connections_path(app: &AppHandle) -> TuskResult<std::path::PathBuf> {
@@ -50,7 +51,7 @@ pub async fn save_connection(app: AppHandle, config: ConnectionConfig) -> TuskRe
#[tauri::command]
pub async fn delete_connection(
app: AppHandle,
state: State<'_, AppState>,
state: State<'_, Arc<AppState>>,
id: String,
) -> TuskResult<()> {
let path = get_connections_path(&app)?;
@@ -91,7 +92,7 @@ pub async fn test_connection(config: ConnectionConfig) -> TuskResult<String> {
}
#[tauri::command]
pub async fn connect(state: State<'_, AppState>, config: ConnectionConfig) -> TuskResult<()> {
pub async fn connect(state: State<'_, Arc<AppState>>, config: ConnectionConfig) -> TuskResult<()> {
let pool = PgPool::connect(&config.connection_url())
.await
.map_err(TuskError::Database)?;
@@ -113,7 +114,7 @@ pub async fn connect(state: State<'_, AppState>, config: ConnectionConfig) -> Tu
#[tauri::command]
pub async fn switch_database(
state: State<'_, AppState>,
state: State<'_, Arc<AppState>>,
config: ConnectionConfig,
database: String,
) -> TuskResult<()> {
@@ -139,7 +140,7 @@ pub async fn switch_database(
}
#[tauri::command]
pub async fn disconnect(state: State<'_, AppState>, id: String) -> TuskResult<()> {
pub async fn disconnect(state: State<'_, Arc<AppState>>, id: String) -> TuskResult<()> {
let mut pools = state.pools.write().await;
if let Some(pool) = pools.remove(&id) {
pool.close().await;
@@ -153,7 +154,7 @@ pub async fn disconnect(state: State<'_, AppState>, id: String) -> TuskResult<()
#[tauri::command]
pub async fn set_read_only(
state: State<'_, AppState>,
state: State<'_, Arc<AppState>>,
connection_id: String,
read_only: bool,
) -> TuskResult<()> {
@@ -164,7 +165,7 @@ pub async fn set_read_only(
#[tauri::command]
pub async fn get_read_only(
state: State<'_, AppState>,
state: State<'_, Arc<AppState>>,
connection_id: String,
) -> TuskResult<bool> {
Ok(state.is_read_only(&connection_id).await)