Skip to main content
Glama
YUChoe

SQLite MCP Server

by YUChoe
DatabaseManager.ts6.8 kB
/** * SQLite 데이터베이스 연결 관리자 * 여러 데이터베이스 파일에 대한 연결을 관리하고 캐싱합니다. */ import sqlite3 from 'sqlite3'; import * as path from 'path'; import * as fs from 'fs'; import { Database, DatabaseManager as IDatabaseManager, QueryResult, TransactionResult, Operation } from '../types/index.js'; /** * SQLite 데이터베이스 매니저 구현 */ export class DatabaseManager implements IDatabaseManager { private connections: Map<string, Database> = new Map(); private readonly maxConnections: number = 50; private readonly connectionTimeout: number = 30 * 60 * 1000; // 30분 /** * 데이터베이스 연결 가져오기 */ getDatabase(dbPath: string): Database { const normalizedPath = this.validateAndNormalizePath(dbPath); const existingDb = this.connections.get(normalizedPath); if (existingDb) { existingDb.lastAccessed = new Date(); return existingDb; } if (this.connections.size >= this.maxConnections) { this.cleanupOldConnections(); } const database = this.createNewConnection(normalizedPath); this.connections.set(normalizedPath, database); return database; } /** * 특정 데이터베이스 연결 닫기 */ closeDatabase(dbPath: string): void { const normalizedPath = this.validateAndNormalizePath(dbPath); const database = this.connections.get(normalizedPath); if (database) { database.connection.close(); this.connections.delete(normalizedPath); } } /** * 모든 데이터베이스 연결 닫기 */ closeAllDatabases(): void { for (const [, database] of this.connections) { database.connection.close(); } this.connections.clear(); } /** * SQL 쿼리 실행 */ async executeQuery(dbPath: string, sql: string, params: any[] = []): Promise<QueryResult> { try { const database = this.getDatabase(dbPath); return new Promise((resolve) => { const sqlLower = sql.trim().toLowerCase(); if (sqlLower.startsWith('select') || sqlLower.startsWith('pragma')) { database.connection.all(sql, params, (err, rows) => { if (err) { resolve({ success: false, error: err.message }); } else { resolve({ success: true, data: rows }); } }); } else { database.connection.run(sql, params, function(err) { if (err) { resolve({ success: false, error: err.message }); } else { const result: QueryResult = { success: true, rowsAffected: this.changes }; if (this.lastID) { result.lastInsertRowid = this.lastID; } resolve(result); } }); } }); } catch (error) { return { success: false, error: (error as Error).message }; } } /** * 트랜잭션 실행 */ async executeTransaction(dbPath: string, operations: Operation[]): Promise<TransactionResult> { const database = this.getDatabase(dbPath); const results: QueryResult[] = []; try { await new Promise<void>((resolve, reject) => { database.connection.exec('BEGIN TRANSACTION', (err) => { if (err) reject(err); else resolve(); }); }); for (const operation of operations) { const result = await this.executeQuery(dbPath, operation.sql, operation.params); results.push(result); if (!result.success) { await new Promise<void>((resolve) => { database.connection.exec('ROLLBACK', () => resolve()); }); return { success: false, results, error: result.error || '알 수 없는 오류가 발생했습니다' }; } } await new Promise<void>((resolve, reject) => { database.connection.exec('COMMIT', (err) => { if (err) reject(err); else resolve(); }); }); return { success: true, results }; } catch (error) { await new Promise<void>((resolve) => { database.connection.exec('ROLLBACK', () => resolve()); }); return { success: false, results, error: (error as Error).message }; } } /** * 경로 검증 및 정규화 */ private validateAndNormalizePath(dbPath: string): string { if (!dbPath || typeof dbPath !== 'string') { throw new Error('데이터베이스 경로가 유효하지 않습니다'); } if (dbPath.includes('..')) { throw new Error('상위 디렉토리 접근은 허용되지 않습니다'); } return path.resolve(dbPath); } /** * 새 데이터베이스 연결 생성 */ private createNewConnection(dbPath: string): Database { try { const dir = path.dirname(dbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const connection = new sqlite3.Database(dbPath); connection.exec('PRAGMA journal_mode = WAL'); return { path: dbPath, connection, lastAccessed: new Date() }; } catch (error) { throw new Error(`데이터베이스 연결 실패: ${(error as Error).message}`); } } /** * 오래된 연결 정리 */ private cleanupOldConnections(): void { const now = new Date(); const connectionsToRemove: string[] = []; for (const [path, database] of this.connections) { const timeDiff = now.getTime() - database.lastAccessed.getTime(); if (timeDiff > this.connectionTimeout) { connectionsToRemove.push(path); } } connectionsToRemove.forEach(dbPath => { const database = this.connections.get(dbPath); if (database) { database.connection.close(); this.connections.delete(dbPath); } }); if (this.connections.size >= this.maxConnections) { const oldestEntry = Array.from(this.connections.entries()) .sort(([, a], [, b]) => a.lastAccessed.getTime() - b.lastAccessed.getTime())[0]; if (oldestEntry) { const [dbPath, database] = oldestEntry; database.connection.close(); this.connections.delete(dbPath); } } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YUChoe/sqlite-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server