Files
session-mcp/src/db.ts
Christian Gick a03e9e065a feat: Add task-mcp server for task management via MCP
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>
2026-01-08 20:58:14 +02:00

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;