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>
This commit is contained in:
120
src/db.ts
Normal file
120
src/db.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user