Implements 10 MCP tools for task management: - CRUD: task_add, task_list, task_show, task_close, task_update - Search: task_similar (pgvector), task_context - Relations: task_link, task_checklist_add, task_checklist_toggle Uses PostgreSQL with pgvector for semantic search via LiteLLM embeddings. Connects via SSH tunnel to docker-host:5435. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
2.8 KiB
TypeScript
121 lines
2.8 KiB
TypeScript
import pg from 'pg';
|
|
const { Pool } = pg;
|
|
|
|
// Configuration from environment variables
|
|
const config = {
|
|
host: process.env.DB_HOST || 'localhost',
|
|
port: parseInt(process.env.DB_PORT || '5432'),
|
|
database: process.env.DB_NAME || 'litellm',
|
|
user: process.env.DB_USER || 'litellm',
|
|
password: process.env.DB_PASSWORD || 'litellm',
|
|
max: 5,
|
|
idleTimeoutMillis: 30000,
|
|
connectionTimeoutMillis: 5000,
|
|
};
|
|
|
|
// Create connection pool
|
|
const pool = new Pool(config);
|
|
|
|
// Log connection errors
|
|
pool.on('error', (err) => {
|
|
console.error('Unexpected database error:', err);
|
|
});
|
|
|
|
/**
|
|
* Execute a query and return all rows
|
|
*/
|
|
export async function query<T = Record<string, unknown>>(
|
|
text: string,
|
|
params?: unknown[]
|
|
): Promise<T[]> {
|
|
const result = await pool.query(text, params);
|
|
return result.rows as T[];
|
|
}
|
|
|
|
/**
|
|
* Execute a query and return the first row
|
|
*/
|
|
export async function queryOne<T = Record<string, unknown>>(
|
|
text: string,
|
|
params?: unknown[]
|
|
): Promise<T | null> {
|
|
const result = await pool.query(text, params);
|
|
return (result.rows[0] as T) || null;
|
|
}
|
|
|
|
/**
|
|
* Execute a query without returning results (INSERT, UPDATE, DELETE)
|
|
*/
|
|
export async function execute(
|
|
text: string,
|
|
params?: unknown[]
|
|
): Promise<number> {
|
|
const result = await pool.query(text, params);
|
|
return result.rowCount || 0;
|
|
}
|
|
|
|
/**
|
|
* Get the next task ID for a project
|
|
*/
|
|
export async function getNextTaskId(projectKey: string): Promise<string> {
|
|
const result = await queryOne<{ next_id: number }>(
|
|
`INSERT INTO task_sequences (project, next_id) VALUES ($1, 1)
|
|
ON CONFLICT (project) DO UPDATE SET next_id = task_sequences.next_id + 1
|
|
RETURNING next_id`,
|
|
[projectKey]
|
|
);
|
|
return `${projectKey}-${result?.next_id || 1}`;
|
|
}
|
|
|
|
/**
|
|
* Get project key from name, or generate one
|
|
*/
|
|
export async function getProjectKey(projectName: string): Promise<string> {
|
|
// First check if already registered
|
|
const existing = await queryOne<{ key: string }>(
|
|
`SELECT key FROM projects WHERE name = $1 LIMIT 1`,
|
|
[projectName]
|
|
);
|
|
|
|
if (existing) {
|
|
return existing.key;
|
|
}
|
|
|
|
// Generate a key from the name (uppercase first letters)
|
|
let generated = projectName.replace(/[a-z]/g, '').slice(0, 4);
|
|
if (!generated) {
|
|
generated = projectName.slice(0, 3).toUpperCase();
|
|
}
|
|
|
|
// Register the new project
|
|
await execute(
|
|
`INSERT INTO projects (key, name) VALUES ($1, $2)
|
|
ON CONFLICT (key) DO NOTHING`,
|
|
[generated, projectName]
|
|
);
|
|
|
|
return generated;
|
|
}
|
|
|
|
/**
|
|
* Test database connection
|
|
*/
|
|
export async function testConnection(): Promise<boolean> {
|
|
try {
|
|
await query('SELECT 1');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Database connection failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close the connection pool
|
|
*/
|
|
export async function close(): Promise<void> {
|
|
await pool.end();
|
|
}
|
|
|
|
export default pool;
|