#![cfg(feature = "mcp-server")]
use sqlx::SqlitePool;
pub(crate) use things3_cli::mcp::ThingsMcpServer;
use things3_core::{config::ThingsConfig, database::ThingsDatabase};
pub(crate) async fn create_test_mcp_server() -> ThingsMcpServer {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let config = ThingsConfig::for_testing().unwrap();
ThingsMcpServer::new(db.into(), config)
}
async fn create_test_schema(db: &ThingsDatabase) -> Result<(), Box<dyn std::error::Error>> {
let pool = db.pool();
sqlx::query(
r"
-- TMTask table (main tasks table) - matches real Things 3 schema
CREATE TABLE IF NOT EXISTS TMTask (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
type INTEGER NOT NULL DEFAULT 0,
status INTEGER NOT NULL DEFAULT 0,
notes TEXT,
startDate INTEGER,
deadline INTEGER,
stopDate REAL,
creationDate REAL NOT NULL,
userModificationDate REAL NOT NULL,
project TEXT,
area TEXT,
heading TEXT,
trashed INTEGER NOT NULL DEFAULT 0,
tags TEXT DEFAULT '[]',
cachedTags BLOB,
todayIndex INTEGER
)
",
)
.execute(pool)
.await?;
sqlx::query(
r"
-- TMArea table (areas table)
CREATE TABLE IF NOT EXISTS TMArea (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
visible INTEGER NOT NULL DEFAULT 1,
'index' INTEGER NOT NULL DEFAULT 0,
creationDate REAL NOT NULL,
userModificationDate REAL NOT NULL
)
",
)
.execute(pool)
.await?;
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTag (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
shortcut TEXT,
parent TEXT,
creationDate REAL NOT NULL,
userModificationDate REAL NOT NULL,
usedDate REAL,
'index' INTEGER NOT NULL DEFAULT 0
)
",
)
.execute(pool)
.await?;
insert_test_data(pool).await?;
Ok(())
}
async fn insert_test_data(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
use chrono::Utc;
use uuid::Uuid;
let _now = Utc::now().to_rfc3339();
let area_uuid = Uuid::new_v4().to_string();
let project_uuid = Uuid::new_v4().to_string();
let task_uuid = Uuid::new_v4().to_string();
let now = chrono::Utc::now().timestamp() as f64;
sqlx::query("INSERT INTO TMArea (uuid, title, visible, 'index', creationDate, userModificationDate) VALUES (?, ?, ?, ?, ?, ?)")
.bind(&area_uuid)
.bind("Work")
.bind(1) .bind(0) .bind(now) .bind(now) .execute(pool)
.await?;
let now_timestamp = 1_700_000_000.0; sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, area, creationDate, userModificationDate, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&project_uuid)
.bind("Website Redesign")
.bind(1) .bind(0) .bind(&area_uuid)
.bind(now_timestamp)
.bind(now_timestamp)
.bind(0) .execute(pool).await?;
let inbox_task_uuid = Uuid::new_v4().to_string();
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, project, creationDate, userModificationDate, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&inbox_task_uuid)
.bind("Inbox Task")
.bind(0) .bind(0) .bind::<Option<String>>(None) .bind(now_timestamp)
.bind(now_timestamp)
.bind(0) .execute(pool).await?;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, project, creationDate, userModificationDate, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&task_uuid)
.bind("Research competitors")
.bind(0) .bind(0) .bind(&project_uuid)
.bind(now_timestamp)
.bind(now_timestamp)
.bind(0) .execute(pool).await?;
Ok(())
}