mirror of
https://github.com/executeautomation/mcp-database-server.git
synced 2025-12-09 21:12:57 +08:00
Added support for SQL Server
This commit is contained in:
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
74
src/db/adapter.ts
Normal file
74
src/db/adapter.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Database adapter interface
|
||||
* Defines the contract for all database implementations (SQLite, SQL Server)
|
||||
*/
|
||||
export interface DbAdapter {
|
||||
/**
|
||||
* Initialize database connection
|
||||
*/
|
||||
init(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
close(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Execute a query and return all results
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
*/
|
||||
all(query: string, params?: any[]): Promise<any[]>;
|
||||
|
||||
/**
|
||||
* Execute a query that modifies data
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
*/
|
||||
run(query: string, params?: any[]): Promise<{ changes: number, lastID: number }>;
|
||||
|
||||
/**
|
||||
* Execute multiple SQL statements
|
||||
* @param query SQL statements to execute
|
||||
*/
|
||||
exec(query: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get database metadata
|
||||
*/
|
||||
getMetadata(): { name: string, type: string, path?: string, server?: string, database?: string };
|
||||
|
||||
/**
|
||||
* Get database-specific query for listing tables
|
||||
*/
|
||||
getListTablesQuery(): string;
|
||||
|
||||
/**
|
||||
* Get database-specific query for describing a table
|
||||
* @param tableName Table name
|
||||
*/
|
||||
getDescribeTableQuery(tableName: string): string;
|
||||
}
|
||||
|
||||
// Import adapters using dynamic imports
|
||||
import { SqliteAdapter } from './sqlite-adapter.js';
|
||||
import { SqlServerAdapter } from './sqlserver-adapter.js';
|
||||
|
||||
/**
|
||||
* Factory function to create the appropriate database adapter
|
||||
*/
|
||||
export function createDbAdapter(type: string, connectionInfo: any): DbAdapter {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'sqlite':
|
||||
// For SQLite, if connectionInfo is a string, use it directly as path
|
||||
if (typeof connectionInfo === 'string') {
|
||||
return new SqliteAdapter(connectionInfo);
|
||||
} else {
|
||||
return new SqliteAdapter(connectionInfo.path);
|
||||
}
|
||||
case 'sqlserver':
|
||||
return new SqlServerAdapter(connectionInfo);
|
||||
default:
|
||||
throw new Error(`Unsupported database type: ${type}`);
|
||||
}
|
||||
}
|
||||
124
src/db/index.ts
124
src/db/index.ts
@@ -1,23 +1,28 @@
|
||||
import sqlite3 from "sqlite3";
|
||||
import { DbAdapter, createDbAdapter } from './adapter.js';
|
||||
|
||||
let db: sqlite3.Database;
|
||||
let databasePath: string;
|
||||
// Store the active database adapter
|
||||
let dbAdapter: DbAdapter | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the SQLite database connection
|
||||
* @param dbPath Path to the SQLite database file
|
||||
* Initialize the database connection
|
||||
* @param connectionInfo Connection information object or SQLite path string
|
||||
* @param dbType Database type ('sqlite' or 'sqlserver')
|
||||
*/
|
||||
export function initDatabase(dbPath: string): Promise<void> {
|
||||
databasePath = dbPath;
|
||||
return new Promise((resolve, reject) => {
|
||||
db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
export async function initDatabase(connectionInfo: any, dbType: string = 'sqlite'): Promise<void> {
|
||||
try {
|
||||
// If connectionInfo is a string, assume it's a SQLite path
|
||||
if (typeof connectionInfo === 'string') {
|
||||
connectionInfo = { path: connectionInfo };
|
||||
}
|
||||
|
||||
// Create appropriate adapter based on database type
|
||||
dbAdapter = createDbAdapter(dbType, connectionInfo);
|
||||
|
||||
// Initialize the connection
|
||||
await dbAdapter.init();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize database: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,15 +32,10 @@ export function initDatabase(dbPath: string): Promise<void> {
|
||||
* @returns Promise with query results
|
||||
*/
|
||||
export function dbAll(query: string, params: any[] = []): Promise<any[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(query, params, (err: Error | null, rows: any[]) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.all(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,15 +45,10 @@ export function dbAll(query: string, params: any[] = []): Promise<any[]> {
|
||||
* @returns Promise with result info
|
||||
*/
|
||||
export function dbRun(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(query, params, function(this: sqlite3.RunResult, err: Error | null) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ changes: this.changes, lastID: this.lastID });
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.run(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,40 +57,49 @@ export function dbRun(query: string, params: any[] = []): Promise<{ changes: num
|
||||
* @returns Promise that resolves when execution completes
|
||||
*/
|
||||
export function dbExec(query: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.exec(query, (err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.exec(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
export function closeDatabase(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
db.close((err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!dbAdapter) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return dbAdapter.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current database path
|
||||
* Get database metadata
|
||||
*/
|
||||
export function getDatabasePath(): string {
|
||||
return databasePath;
|
||||
export function getDatabaseMetadata(): { name: string, type: string, path?: string, server?: string, database?: string } {
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.getMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for listing tables
|
||||
*/
|
||||
export function getListTablesQuery(): string {
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.getListTablesQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for describing a table
|
||||
* @param tableName Table name
|
||||
*/
|
||||
export function getDescribeTableQuery(tableName: string): string {
|
||||
if (!dbAdapter) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
return dbAdapter.getDescribeTableQuery(tableName);
|
||||
}
|
||||
145
src/db/sqlite-adapter.ts
Normal file
145
src/db/sqlite-adapter.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import sqlite3 from "sqlite3";
|
||||
import { DbAdapter } from "./adapter.js";
|
||||
|
||||
/**
|
||||
* SQLite database adapter implementation
|
||||
*/
|
||||
export class SqliteAdapter implements DbAdapter {
|
||||
private db: sqlite3.Database | null = null;
|
||||
private dbPath: string;
|
||||
|
||||
constructor(dbPath: string) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the SQLite database connection
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Ensure the dbPath is accessible
|
||||
console.log(`Opening SQLite database at: ${this.dbPath}`);
|
||||
this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
||||
if (err) {
|
||||
console.error(`SQLite connection error: ${err.message}`);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log("SQLite database opened successfully");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query and get all results
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with query results
|
||||
*/
|
||||
async all(query: string, params: any[] = []): Promise<any[]> {
|
||||
if (!this.db) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db!.all(query, params, (err: Error | null, rows: any[]) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query that modifies data
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with result info
|
||||
*/
|
||||
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||
if (!this.db) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db!.run(query, params, function(this: sqlite3.RunResult, err: Error | null) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ changes: this.changes, lastID: this.lastID });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple SQL statements
|
||||
* @param query SQL statements to execute
|
||||
* @returns Promise that resolves when execution completes
|
||||
*/
|
||||
async exec(query: string): Promise<void> {
|
||||
if (!this.db) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db!.exec(query, (err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.db) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.db.close((err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
this.db = null;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database metadata
|
||||
*/
|
||||
getMetadata(): { name: string, type: string, path: string } {
|
||||
return {
|
||||
name: "SQLite",
|
||||
type: "sqlite",
|
||||
path: this.dbPath
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for listing tables
|
||||
*/
|
||||
getListTablesQuery(): string {
|
||||
return "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for describing a table
|
||||
* @param tableName Table name
|
||||
*/
|
||||
getDescribeTableQuery(tableName: string): string {
|
||||
return `PRAGMA table_info(${tableName})`;
|
||||
}
|
||||
}
|
||||
212
src/db/sqlserver-adapter.ts
Normal file
212
src/db/sqlserver-adapter.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { DbAdapter } from "./adapter.js";
|
||||
import sql from 'mssql';
|
||||
|
||||
/**
|
||||
* SQL Server database adapter implementation
|
||||
*/
|
||||
export class SqlServerAdapter implements DbAdapter {
|
||||
private pool: sql.ConnectionPool | null = null;
|
||||
private config: sql.config;
|
||||
private server: string;
|
||||
private database: string;
|
||||
|
||||
constructor(connectionInfo: {
|
||||
server: string;
|
||||
database: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
port?: number;
|
||||
trustServerCertificate?: boolean;
|
||||
options?: any;
|
||||
}) {
|
||||
this.server = connectionInfo.server;
|
||||
this.database = connectionInfo.database;
|
||||
|
||||
// Create SQL Server connection config
|
||||
this.config = {
|
||||
server: connectionInfo.server,
|
||||
database: connectionInfo.database,
|
||||
port: connectionInfo.port || 1433,
|
||||
options: {
|
||||
trustServerCertificate: connectionInfo.trustServerCertificate ?? true,
|
||||
...connectionInfo.options
|
||||
}
|
||||
};
|
||||
|
||||
// Add authentication options
|
||||
if (connectionInfo.user && connectionInfo.password) {
|
||||
this.config.user = connectionInfo.user;
|
||||
this.config.password = connectionInfo.password;
|
||||
} else {
|
||||
// Use Windows authentication if no username/password provided
|
||||
this.config.options!.trustedConnection = true;
|
||||
this.config.options!.enableArithAbort = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize SQL Server connection
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
this.pool = await new sql.ConnectionPool(this.config).connect();
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to connect to SQL Server: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query and get all results
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with query results
|
||||
*/
|
||||
async all(query: string, params: any[] = []): Promise<any[]> {
|
||||
if (!this.pool) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
const request = this.pool.request();
|
||||
|
||||
// Add parameters to the request
|
||||
params.forEach((param, index) => {
|
||||
request.input(`param${index}`, param);
|
||||
});
|
||||
|
||||
// Replace ? with named parameters
|
||||
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
||||
|
||||
const result = await request.query(preparedQuery);
|
||||
return result.recordset;
|
||||
} catch (err) {
|
||||
throw new Error(`SQL Server query error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query that modifies data
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @returns Promise with result info
|
||||
*/
|
||||
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||
if (!this.pool) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
const request = this.pool.request();
|
||||
|
||||
// Add parameters to the request
|
||||
params.forEach((param, index) => {
|
||||
request.input(`param${index}`, param);
|
||||
});
|
||||
|
||||
// Replace ? with named parameters
|
||||
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
||||
|
||||
// Add output parameter for identity value if it's an INSERT
|
||||
let lastID = 0;
|
||||
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
||||
request.output('insertedId', sql.Int, 0);
|
||||
const updatedQuery = `${preparedQuery}; SELECT @insertedId = SCOPE_IDENTITY();`;
|
||||
const result = await request.query(updatedQuery);
|
||||
lastID = result.output.insertedId || 0;
|
||||
} else {
|
||||
const result = await request.query(preparedQuery);
|
||||
lastID = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
changes: this.getAffectedRows(query, lastID),
|
||||
lastID: lastID
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(`SQL Server query error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple SQL statements
|
||||
* @param query SQL statements to execute
|
||||
* @returns Promise that resolves when execution completes
|
||||
*/
|
||||
async exec(query: string): Promise<void> {
|
||||
if (!this.pool) {
|
||||
throw new Error("Database not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
const request = this.pool.request();
|
||||
await request.batch(query);
|
||||
} catch (err) {
|
||||
throw new Error(`SQL Server batch error: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
if (this.pool) {
|
||||
await this.pool.close();
|
||||
this.pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database metadata
|
||||
*/
|
||||
getMetadata(): { name: string, type: string, server: string, database: string } {
|
||||
return {
|
||||
name: "SQL Server",
|
||||
type: "sqlserver",
|
||||
server: this.server,
|
||||
database: this.database
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for listing tables
|
||||
*/
|
||||
getListTablesQuery(): string {
|
||||
return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-specific query for describing a table
|
||||
* @param tableName Table name
|
||||
*/
|
||||
getDescribeTableQuery(tableName: string): string {
|
||||
return `
|
||||
SELECT
|
||||
c.COLUMN_NAME as name,
|
||||
c.DATA_TYPE as type,
|
||||
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END as notnull,
|
||||
CASE WHEN pk.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 1 ELSE 0 END as pk,
|
||||
c.COLUMN_DEFAULT as dflt_value
|
||||
FROM
|
||||
INFORMATION_SCHEMA.COLUMNS c
|
||||
LEFT JOIN
|
||||
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.TABLE_NAME = kcu.TABLE_NAME AND c.COLUMN_NAME = kcu.COLUMN_NAME
|
||||
LEFT JOIN
|
||||
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ON kcu.CONSTRAINT_NAME = pk.CONSTRAINT_NAME AND pk.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
WHERE
|
||||
c.TABLE_NAME = '${tableName}'
|
||||
ORDER BY
|
||||
c.ORDINAL_POSITION
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the number of affected rows based on query type
|
||||
*/
|
||||
private getAffectedRows(query: string, lastID: number): number {
|
||||
const queryType = query.trim().split(' ')[0].toUpperCase();
|
||||
if (queryType === 'INSERT' && lastID > 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0; // For SELECT, unknown for UPDATE/DELETE without additional query
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { dbAll } from '../db/index.js';
|
||||
import { getDatabasePath } from '../db/index.js';
|
||||
import { dbAll, getListTablesQuery, getDescribeTableQuery, getDatabaseMetadata } from '../db/index.js';
|
||||
|
||||
/**
|
||||
* Handle listing resources request
|
||||
@@ -7,13 +6,24 @@ import { getDatabasePath } from '../db/index.js';
|
||||
*/
|
||||
export async function handleListResources() {
|
||||
try {
|
||||
const databasePath = getDatabasePath();
|
||||
const resourceBaseUrl = new URL(`sqlite:///${databasePath}`);
|
||||
const dbInfo = getDatabaseMetadata();
|
||||
const dbType = dbInfo.type;
|
||||
let resourceBaseUrl: URL;
|
||||
|
||||
// Create appropriate URL based on database type
|
||||
if (dbType === 'sqlite' && dbInfo.path) {
|
||||
resourceBaseUrl = new URL(`sqlite:///${dbInfo.path}`);
|
||||
} else if (dbType === 'sqlserver' && dbInfo.server && dbInfo.database) {
|
||||
resourceBaseUrl = new URL(`sqlserver://${dbInfo.server}/${dbInfo.database}`);
|
||||
} else {
|
||||
resourceBaseUrl = new URL(`db:///database`);
|
||||
}
|
||||
|
||||
const SCHEMA_PATH = "schema";
|
||||
|
||||
const result = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
// Use adapter-specific query to list tables
|
||||
const query = getListTablesQuery();
|
||||
const result = await dbAll(query);
|
||||
|
||||
return {
|
||||
resources: result.map((row: any) => ({
|
||||
@@ -45,8 +55,9 @@ export async function handleReadResource(uri: string) {
|
||||
throw new Error("Invalid resource URI");
|
||||
}
|
||||
|
||||
// Query to get column information for a table
|
||||
const result = await dbAll(`PRAGMA table_info("${tableName}")`);
|
||||
// Use adapter-specific query to describe the table
|
||||
const query = getDescribeTableQuery(tableName!);
|
||||
const result = await dbAll(query);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
|
||||
75
src/index.ts
75
src/index.ts
@@ -10,7 +10,7 @@ import {
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
// Import database utils
|
||||
import { initDatabase, closeDatabase } from './db/index.js';
|
||||
import { initDatabase, closeDatabase, getDatabaseMetadata } from './db/index.js';
|
||||
|
||||
// Import handlers
|
||||
import { handleListResources, handleReadResource } from './handlers/resourceHandlers.js';
|
||||
@@ -33,11 +33,52 @@ const server = new Server(
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error("Please provide a database file path as a command-line argument");
|
||||
console.error("Please provide database connection information");
|
||||
console.error("Usage for SQLite: node index.js <database_file_path>");
|
||||
console.error("Usage for SQL Server: node index.js --sqlserver --server <server> --database <database> [--user <user> --password <password>]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const databasePath = args[0];
|
||||
// Parse arguments to determine database type and connection info
|
||||
let dbType = 'sqlite';
|
||||
let connectionInfo: any = null;
|
||||
|
||||
// Check if using SQL Server
|
||||
if (args.includes('--sqlserver')) {
|
||||
dbType = 'sqlserver';
|
||||
connectionInfo = {
|
||||
server: '',
|
||||
database: '',
|
||||
user: undefined,
|
||||
password: undefined
|
||||
};
|
||||
|
||||
// Parse SQL Server connection parameters
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--server' && i + 1 < args.length) {
|
||||
connectionInfo.server = args[i + 1];
|
||||
} else if (args[i] === '--database' && i + 1 < args.length) {
|
||||
connectionInfo.database = args[i + 1];
|
||||
} else if (args[i] === '--user' && i + 1 < args.length) {
|
||||
connectionInfo.user = args[i + 1];
|
||||
} else if (args[i] === '--password' && i + 1 < args.length) {
|
||||
connectionInfo.password = args[i + 1];
|
||||
} else if (args[i] === '--port' && i + 1 < args.length) {
|
||||
connectionInfo.port = parseInt(args[i + 1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate SQL Server connection info
|
||||
if (!connectionInfo.server || !connectionInfo.database) {
|
||||
console.error("Error: SQL Server requires --server and --database parameters");
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// SQLite mode (default)
|
||||
dbType = 'sqlite';
|
||||
connectionInfo = args[0]; // First argument is the SQLite file path
|
||||
console.log(`Using SQLite database at path: ${connectionInfo}`);
|
||||
}
|
||||
|
||||
// Set up request handlers
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
@@ -69,13 +110,32 @@ process.on('SIGTERM', async () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Add global error handler
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught exception:', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*/
|
||||
async function runServer() {
|
||||
try {
|
||||
console.log(`Initializing database: ${databasePath}`);
|
||||
await initDatabase(databasePath);
|
||||
console.log(`Initializing ${dbType} database...`);
|
||||
if (dbType === 'sqlite') {
|
||||
console.log(`Database path: ${connectionInfo}`);
|
||||
} else if (dbType === 'sqlserver') {
|
||||
console.log(`Server: ${connectionInfo.server}, Database: ${connectionInfo.database}`);
|
||||
}
|
||||
|
||||
// Initialize the database
|
||||
await initDatabase(connectionInfo, dbType);
|
||||
|
||||
const dbInfo = getDatabaseMetadata();
|
||||
console.log(`Connected to ${dbInfo.name} database`);
|
||||
|
||||
console.log('Starting MCP server...');
|
||||
const transport = new StdioServerTransport();
|
||||
@@ -89,4 +149,7 @@ async function runServer() {
|
||||
}
|
||||
|
||||
// Start the server
|
||||
runServer().catch(console.error);
|
||||
runServer().catch(error => {
|
||||
console.error("Server initialization failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dbAll, dbExec } from '../db/index.js';
|
||||
import { dbAll, dbExec, getListTablesQuery, getDescribeTableQuery } from '../db/index.js';
|
||||
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
||||
|
||||
/**
|
||||
@@ -56,13 +56,12 @@ export async function dropTable(tableName: string, confirm: boolean) {
|
||||
});
|
||||
}
|
||||
|
||||
// Check if table exists
|
||||
const tableExists = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
||||
[tableName]
|
||||
);
|
||||
// First check if table exists by directly querying for tables
|
||||
const query = getListTablesQuery();
|
||||
const tables = await dbAll(query);
|
||||
const tableNames = tables.map(t => t.name);
|
||||
|
||||
if (tableExists.length === 0) {
|
||||
if (!tableNames.includes(tableName)) {
|
||||
throw new Error(`Table '${tableName}' does not exist`);
|
||||
}
|
||||
|
||||
@@ -84,9 +83,9 @@ export async function dropTable(tableName: string, confirm: boolean) {
|
||||
*/
|
||||
export async function listTables() {
|
||||
try {
|
||||
const tables = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
// Use adapter-specific query for listing tables
|
||||
const query = getListTablesQuery();
|
||||
const tables = await dbAll(query);
|
||||
return formatSuccessResponse(tables.map((t) => t.name));
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error listing tables: ${error.message}`);
|
||||
@@ -104,17 +103,19 @@ export async function describeTable(tableName: string) {
|
||||
throw new Error("Table name is required");
|
||||
}
|
||||
|
||||
// Check if table exists
|
||||
const tableExists = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
||||
[tableName]
|
||||
);
|
||||
// First check if table exists by directly querying for tables
|
||||
const query = getListTablesQuery();
|
||||
const tables = await dbAll(query);
|
||||
const tableNames = tables.map(t => t.name);
|
||||
|
||||
if (tableExists.length === 0) {
|
||||
if (!tableNames.includes(tableName)) {
|
||||
throw new Error(`Table '${tableName}' does not exist`);
|
||||
}
|
||||
|
||||
const columns = await dbAll(`PRAGMA table_info("${tableName}")`);
|
||||
// Use adapter-specific query for describing tables
|
||||
const descQuery = getDescribeTableQuery(tableName);
|
||||
const columns = await dbAll(descQuery);
|
||||
|
||||
return formatSuccessResponse(columns.map((col) => ({
|
||||
name: col.name,
|
||||
type: col.type,
|
||||
|
||||
Reference in New Issue
Block a user