mirror of
https://github.com/executeautomation/mcp-database-server.git
synced 2025-12-09 21:12:57 +08:00
Refactor code for extensability
Refactor code for extensability
This commit is contained in:
5
build.sh
5
build.sh
@@ -16,6 +16,7 @@ echo "Building TypeScript..."
|
||||
|
||||
# Make JavaScript files executable
|
||||
echo "Making JavaScript files executable..."
|
||||
chmod +x dist/*.js
|
||||
chmod +x dist/src/index.js
|
||||
|
||||
echo "Build completed successfully!"
|
||||
echo "Build completed successfully!"
|
||||
echo "You can now run the server with: node dist/src/index.js /path/to/your/database.db"
|
||||
12
package.json
12
package.json
@@ -8,18 +8,19 @@
|
||||
"bugs": "https://github.com/executeautomation/database-server/issues",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"ea-database-server": "dist/index.js"
|
||||
"ea-database-server": "dist/src/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && shx chmod +x dist/*.js",
|
||||
"build": "tsc && shx chmod +x dist/src/index.js",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsc && node dist/index.js",
|
||||
"example": "node examples/example.js"
|
||||
"start": "node dist/src/index.js",
|
||||
"dev": "tsc && node dist/src/index.js",
|
||||
"example": "node examples/example.js",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.9.0",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sqlite3": "5.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"shx": "0.4.0",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
|
||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
101
src/db/index.ts
Normal file
101
src/db/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import sqlite3 from "sqlite3";
|
||||
|
||||
let db: sqlite3.Database;
|
||||
let databasePath: string;
|
||||
|
||||
/**
|
||||
* Initialize the SQLite database connection
|
||||
* @param dbPath Path to the SQLite database file
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query and get all results
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query that modifies data
|
||||
* @param query SQL query to execute
|
||||
* @param params Query parameters
|
||||
* @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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple SQL statements
|
||||
* @param query SQL statements to execute
|
||||
* @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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current database path
|
||||
*/
|
||||
export function getDatabasePath(): string {
|
||||
return databasePath;
|
||||
}
|
||||
66
src/handlers/resourceHandlers.ts
Normal file
66
src/handlers/resourceHandlers.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { dbAll } from '../db/index.js';
|
||||
import { getDatabasePath } from '../db/index.js';
|
||||
|
||||
/**
|
||||
* Handle listing resources request
|
||||
* @returns List of available resources
|
||||
*/
|
||||
export async function handleListResources() {
|
||||
try {
|
||||
const databasePath = getDatabasePath();
|
||||
const resourceBaseUrl = new URL(`sqlite:///${databasePath}`);
|
||||
const SCHEMA_PATH = "schema";
|
||||
|
||||
const result = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
|
||||
return {
|
||||
resources: result.map((row: any) => ({
|
||||
uri: new URL(`${row.name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
|
||||
mimeType: "application/json",
|
||||
name: `"${row.name}" database schema`,
|
||||
})),
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error listing resources: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reading a specific resource
|
||||
* @param uri URI of the resource to read
|
||||
* @returns Resource contents
|
||||
*/
|
||||
export async function handleReadResource(uri: string) {
|
||||
try {
|
||||
const resourceUrl = new URL(uri);
|
||||
const SCHEMA_PATH = "schema";
|
||||
|
||||
const pathComponents = resourceUrl.pathname.split("/");
|
||||
const schema = pathComponents.pop();
|
||||
const tableName = pathComponents.pop();
|
||||
|
||||
if (schema !== SCHEMA_PATH) {
|
||||
throw new Error("Invalid resource URI");
|
||||
}
|
||||
|
||||
// Query to get column information for a table
|
||||
const result = await dbAll(`PRAGMA table_info("${tableName}")`);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(result.map((column: any) => ({
|
||||
column_name: column.name,
|
||||
data_type: column.type
|
||||
})), null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error reading resource: ${error.message}`);
|
||||
}
|
||||
}
|
||||
170
src/handlers/toolHandlers.ts
Normal file
170
src/handlers/toolHandlers.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { formatErrorResponse } from '../utils/formatUtils.js';
|
||||
|
||||
// Import all tool implementations
|
||||
import { readQuery, writeQuery, exportQuery } from '../tools/queryTools.js';
|
||||
import { createTable, alterTable, dropTable, listTables, describeTable } from '../tools/schemaTools.js';
|
||||
import { appendInsight, listInsights } from '../tools/insightTools.js';
|
||||
|
||||
/**
|
||||
* Handle listing available tools
|
||||
* @returns List of available tools
|
||||
*/
|
||||
export function handleListTools() {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "read_query",
|
||||
description: "Execute SELECT queries to read data from the database",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "write_query",
|
||||
description: "Execute INSERT, UPDATE, or DELETE queries",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create_table",
|
||||
description: "Create new tables in the database",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "alter_table",
|
||||
description: "Modify existing table schema (add columns, rename tables, etc.)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop_table",
|
||||
description: "Remove a table from the database with safety confirmation",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
table_name: { type: "string" },
|
||||
confirm: { type: "boolean" },
|
||||
},
|
||||
required: ["table_name", "confirm"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export_query",
|
||||
description: "Export query results to various formats (CSV, JSON)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
format: { type: "string", enum: ["csv", "json"] },
|
||||
},
|
||||
required: ["query", "format"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_tables",
|
||||
description: "Get a list of all tables in the database",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "describe_table",
|
||||
description: "View schema information for a specific table",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
table_name: { type: "string" },
|
||||
},
|
||||
required: ["table_name"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "append_insight",
|
||||
description: "Add a business insight to the memo",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
insight: { type: "string" },
|
||||
},
|
||||
required: ["insight"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_insights",
|
||||
description: "List all business insights in the memo",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool call requests
|
||||
* @param name Name of the tool to call
|
||||
* @param args Arguments for the tool
|
||||
* @returns Tool execution result
|
||||
*/
|
||||
export async function handleToolCall(name: string, args: any) {
|
||||
try {
|
||||
switch (name) {
|
||||
case "read_query":
|
||||
return await readQuery(args.query);
|
||||
|
||||
case "write_query":
|
||||
return await writeQuery(args.query);
|
||||
|
||||
case "create_table":
|
||||
return await createTable(args.query);
|
||||
|
||||
case "alter_table":
|
||||
return await alterTable(args.query);
|
||||
|
||||
case "drop_table":
|
||||
return await dropTable(args.table_name, args.confirm);
|
||||
|
||||
case "export_query":
|
||||
return await exportQuery(args.query, args.format);
|
||||
|
||||
case "list_tables":
|
||||
return await listTables();
|
||||
|
||||
case "describe_table":
|
||||
return await describeTable(args.table_name);
|
||||
|
||||
case "append_insight":
|
||||
return await appendInsight(args.insight);
|
||||
|
||||
case "list_insights":
|
||||
return await listInsights();
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
return formatErrorResponse(error);
|
||||
}
|
||||
}
|
||||
92
src/index.ts
Normal file
92
src/index.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
// Import database utils
|
||||
import { initDatabase, closeDatabase } from './db/index.js';
|
||||
|
||||
// Import handlers
|
||||
import { handleListResources, handleReadResource } from './handlers/resourceHandlers.js';
|
||||
import { handleListTools, handleToolCall } from './handlers/toolHandlers.js';
|
||||
|
||||
// Configure the server
|
||||
const server = new Server(
|
||||
{
|
||||
name: "executeautomation/database-server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 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");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const databasePath = args[0];
|
||||
|
||||
// Set up request handlers
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return await handleListResources();
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
return await handleReadResource(request.params.uri);
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return handleListTools();
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
return await handleToolCall(request.params.name, request.params.arguments);
|
||||
});
|
||||
|
||||
// Handle shutdown gracefully
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('Shutting down gracefully...');
|
||||
await closeDatabase();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Shutting down gracefully...');
|
||||
await closeDatabase();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*/
|
||||
async function runServer() {
|
||||
try {
|
||||
console.log(`Initializing database: ${databasePath}`);
|
||||
await initDatabase(databasePath);
|
||||
|
||||
console.log('Starting MCP server...');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
console.log('Server running. Press Ctrl+C to exit.');
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server
|
||||
runServer().catch(console.error);
|
||||
64
src/tools/insightTools.ts
Normal file
64
src/tools/insightTools.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { dbAll, dbExec, dbRun } from '../db/index.js';
|
||||
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
||||
|
||||
/**
|
||||
* Add a business insight to the memo
|
||||
* @param insight Business insight text
|
||||
* @returns Result of the operation
|
||||
*/
|
||||
export async function appendInsight(insight: string) {
|
||||
try {
|
||||
if (!insight) {
|
||||
throw new Error("Insight text is required");
|
||||
}
|
||||
|
||||
// Create insights table if it doesn't exist
|
||||
await dbExec(`
|
||||
CREATE TABLE IF NOT EXISTS mcp_insights (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
insight TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// Insert the insight
|
||||
await dbRun(
|
||||
"INSERT INTO mcp_insights (insight) VALUES (?)",
|
||||
[insight]
|
||||
);
|
||||
|
||||
return formatSuccessResponse({ success: true, message: "Insight added" });
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error adding insight: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all insights in the memo
|
||||
* @returns Array of insights
|
||||
*/
|
||||
export async function listInsights() {
|
||||
try {
|
||||
// Check if insights table exists
|
||||
const tableExists = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = 'mcp_insights'"
|
||||
);
|
||||
|
||||
if (tableExists.length === 0) {
|
||||
// Create table if it doesn't exist
|
||||
await dbExec(`
|
||||
CREATE TABLE IF NOT EXISTS mcp_insights (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
insight TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
return formatSuccessResponse([]);
|
||||
}
|
||||
|
||||
const insights = await dbAll("SELECT * FROM mcp_insights ORDER BY created_at DESC");
|
||||
return formatSuccessResponse(insights);
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error listing insights: ${error.message}`);
|
||||
}
|
||||
}
|
||||
77
src/tools/queryTools.ts
Normal file
77
src/tools/queryTools.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { dbAll, dbRun, dbExec } from '../db/index.js';
|
||||
import { formatErrorResponse, formatSuccessResponse, convertToCSV } from '../utils/formatUtils.js';
|
||||
|
||||
/**
|
||||
* Execute a read-only SQL query
|
||||
* @param query SQL query to execute
|
||||
* @returns Query results
|
||||
*/
|
||||
export async function readQuery(query: string) {
|
||||
try {
|
||||
if (!query.trim().toLowerCase().startsWith("select")) {
|
||||
throw new Error("Only SELECT queries are allowed with read_query");
|
||||
}
|
||||
|
||||
const result = await dbAll(query);
|
||||
return formatSuccessResponse(result);
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a data modification SQL query
|
||||
* @param query SQL query to execute
|
||||
* @returns Information about affected rows
|
||||
*/
|
||||
export async function writeQuery(query: string) {
|
||||
try {
|
||||
const lowerQuery = query.trim().toLowerCase();
|
||||
|
||||
if (lowerQuery.startsWith("select")) {
|
||||
throw new Error("Use read_query for SELECT operations");
|
||||
}
|
||||
|
||||
if (!(lowerQuery.startsWith("insert") || lowerQuery.startsWith("update") || lowerQuery.startsWith("delete"))) {
|
||||
throw new Error("Only INSERT, UPDATE, or DELETE operations are allowed with write_query");
|
||||
}
|
||||
|
||||
const result = await dbRun(query);
|
||||
return formatSuccessResponse({ affected_rows: result.changes });
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export query results to CSV or JSON format
|
||||
* @param query SQL query to execute
|
||||
* @param format Output format (csv or json)
|
||||
* @returns Formatted query results
|
||||
*/
|
||||
export async function exportQuery(query: string, format: string) {
|
||||
try {
|
||||
if (!query.trim().toLowerCase().startsWith("select")) {
|
||||
throw new Error("Only SELECT queries are allowed with export_query");
|
||||
}
|
||||
|
||||
const result = await dbAll(query);
|
||||
|
||||
if (format === "csv") {
|
||||
const csvData = convertToCSV(result);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: csvData
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} else if (format === "json") {
|
||||
return formatSuccessResponse(result);
|
||||
} else {
|
||||
throw new Error("Unsupported export format. Use 'csv' or 'json'");
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Export Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
128
src/tools/schemaTools.ts
Normal file
128
src/tools/schemaTools.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { dbAll, dbExec } from '../db/index.js';
|
||||
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
||||
|
||||
/**
|
||||
* Create a new table in the database
|
||||
* @param query CREATE TABLE SQL statement
|
||||
* @returns Result of the operation
|
||||
*/
|
||||
export async function createTable(query: string) {
|
||||
try {
|
||||
if (!query.trim().toLowerCase().startsWith("create table")) {
|
||||
throw new Error("Only CREATE TABLE statements are allowed");
|
||||
}
|
||||
|
||||
await dbExec(query);
|
||||
return formatSuccessResponse({ success: true, message: "Table created successfully" });
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter an existing table schema
|
||||
* @param query ALTER TABLE SQL statement
|
||||
* @returns Result of the operation
|
||||
*/
|
||||
export async function alterTable(query: string) {
|
||||
try {
|
||||
if (!query.trim().toLowerCase().startsWith("alter table")) {
|
||||
throw new Error("Only ALTER TABLE statements are allowed");
|
||||
}
|
||||
|
||||
await dbExec(query);
|
||||
return formatSuccessResponse({ success: true, message: "Table altered successfully" });
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a table from the database
|
||||
* @param tableName Name of the table to drop
|
||||
* @param confirm Safety confirmation flag
|
||||
* @returns Result of the operation
|
||||
*/
|
||||
export async function dropTable(tableName: string, confirm: boolean) {
|
||||
try {
|
||||
if (!tableName) {
|
||||
throw new Error("Table name is required");
|
||||
}
|
||||
|
||||
if (!confirm) {
|
||||
return formatSuccessResponse({
|
||||
success: false,
|
||||
message: "Safety confirmation required. Set confirm=true to proceed with dropping the table."
|
||||
});
|
||||
}
|
||||
|
||||
// Check if table exists
|
||||
const tableExists = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
||||
[tableName]
|
||||
);
|
||||
|
||||
if (tableExists.length === 0) {
|
||||
throw new Error(`Table '${tableName}' does not exist`);
|
||||
}
|
||||
|
||||
// Drop the table
|
||||
await dbExec(`DROP TABLE "${tableName}"`);
|
||||
|
||||
return formatSuccessResponse({
|
||||
success: true,
|
||||
message: `Table '${tableName}' dropped successfully`
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error dropping table: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all tables in the database
|
||||
* @returns Array of table names
|
||||
*/
|
||||
export async function listTables() {
|
||||
try {
|
||||
const tables = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
return formatSuccessResponse(tables.map((t) => t.name));
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error listing tables: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema information for a specific table
|
||||
* @param tableName Name of the table to describe
|
||||
* @returns Column definitions for the table
|
||||
*/
|
||||
export async function describeTable(tableName: string) {
|
||||
try {
|
||||
if (!tableName) {
|
||||
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]
|
||||
);
|
||||
|
||||
if (tableExists.length === 0) {
|
||||
throw new Error(`Table '${tableName}' does not exist`);
|
||||
}
|
||||
|
||||
const columns = await dbAll(`PRAGMA table_info("${tableName}")`);
|
||||
return formatSuccessResponse(columns.map((col) => ({
|
||||
name: col.name,
|
||||
type: col.type,
|
||||
notnull: !!col.notnull,
|
||||
default_value: col.dflt_value,
|
||||
primary_key: !!col.pk
|
||||
})));
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error describing table: ${error.message}`);
|
||||
}
|
||||
}
|
||||
61
src/utils/formatUtils.ts
Normal file
61
src/utils/formatUtils.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Convert data to CSV format
|
||||
* @param data Array of objects to convert to CSV
|
||||
* @returns CSV formatted string
|
||||
*/
|
||||
export function convertToCSV(data: any[]): string {
|
||||
if (data.length === 0) return '';
|
||||
|
||||
// Get headers
|
||||
const headers = Object.keys(data[0]);
|
||||
|
||||
// Create CSV header row
|
||||
let csv = headers.join(',') + '\n';
|
||||
|
||||
// Add data rows
|
||||
data.forEach(row => {
|
||||
const values = headers.map(header => {
|
||||
const val = row[header];
|
||||
// Handle strings with commas, quotes, etc.
|
||||
if (typeof val === 'string') {
|
||||
return `"${val.replace(/"/g, '""')}"`;
|
||||
}
|
||||
// Use empty string for null/undefined
|
||||
return val === null || val === undefined ? '' : val;
|
||||
});
|
||||
csv += values.join(',') + '\n';
|
||||
});
|
||||
|
||||
return csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error response
|
||||
* @param error Error object or message
|
||||
* @returns Formatted error response object
|
||||
*/
|
||||
export function formatErrorResponse(error: Error | string): { content: Array<{type: string, text: string}>, isError: boolean } {
|
||||
const message = error instanceof Error ? error.message : error;
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ error: message }, null, 2)
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format success response
|
||||
* @param data Data to format
|
||||
* @returns Formatted success response object
|
||||
*/
|
||||
export function formatSuccessResponse(data: any): { content: Array<{type: string, text: string}>, isError: boolean } {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}],
|
||||
isError: false
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"declaration": false
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
Reference in New Issue
Block a user