mirror of
https://github.com/executeautomation/mcp-database-server.git
synced 2025-12-09 21:12:57 +08:00
Initial Commit
This commit is contained in:
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
# Add metadata
|
||||
LABEL maintainer="ExecuteAutomation <info@executeautomation.com>"
|
||||
LABEL description="ExecuteAutomation Database Server - A Model Context Protocol server for SQLite"
|
||||
LABEL version="1.0.0"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the TypeScript code
|
||||
RUN npm run build
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
|
||||
# Default command (to be overridden by the user)
|
||||
CMD [""]
|
||||
21
build.sh
Executable file
21
build.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Clean dist directory
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
|
||||
# Install dependencies if node_modules doesn't exist
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Build TypeScript code
|
||||
echo "Building TypeScript..."
|
||||
./node_modules/.bin/tsc
|
||||
|
||||
# Make JavaScript files executable
|
||||
echo "Making JavaScript files executable..."
|
||||
chmod +x dist/*.js
|
||||
|
||||
echo "Build completed successfully!"
|
||||
63
examples/example.js
Normal file
63
examples/example.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// Example of using the SQLite MCP Server
|
||||
|
||||
// First, start the server with a SQLite database file:
|
||||
// npx @modelcontextprotocol/server-sqlite ./example.db
|
||||
|
||||
// Then, use the Claude Desktop app with the following config:
|
||||
/*
|
||||
{
|
||||
"mcpServers": {
|
||||
"sqlite": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-sqlite",
|
||||
"./example.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Example prompts to use with Claude:
|
||||
|
||||
/*
|
||||
1. Create a table:
|
||||
"Create a 'users' table with columns for id, name, email, and created_at"
|
||||
|
||||
2. Insert data:
|
||||
"Insert a few sample users into the users table"
|
||||
|
||||
3. Query data:
|
||||
"Show me all users in the database"
|
||||
|
||||
4. Describe the schema:
|
||||
"What tables exist in the database and what are their structures?"
|
||||
|
||||
5. Add an insight:
|
||||
"Analyze the user data and add an insight about the user distribution"
|
||||
*/
|
||||
|
||||
// SQLite setup
|
||||
// 1. Create table example
|
||||
/*
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
*/
|
||||
|
||||
// 2. Insert data example
|
||||
/*
|
||||
INSERT INTO users (name, email) VALUES
|
||||
('John Doe', 'john@example.com'),
|
||||
('Jane Smith', 'jane@example.com'),
|
||||
('Bob Johnson', 'bob@example.com')
|
||||
*/
|
||||
|
||||
// 3. Query example
|
||||
/*
|
||||
SELECT * FROM users
|
||||
*/
|
||||
34
global.d.ts
vendored
Normal file
34
global.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
declare module 'sqlite3' {
|
||||
export interface RunResult {
|
||||
lastID: number;
|
||||
changes: number;
|
||||
}
|
||||
|
||||
export interface Database {
|
||||
all(sql: string, params: any[], callback: (err: Error | null, rows: any[]) => void): void;
|
||||
all(sql: string, callback: (err: Error | null, rows: any[]) => void): void;
|
||||
|
||||
get(sql: string, params: any[], callback: (err: Error | null, row: any) => void): void;
|
||||
get(sql: string, callback: (err: Error | null, row: any) => void): void;
|
||||
|
||||
run(sql: string, params: any[], callback?: (err: Error | null) => void): this;
|
||||
run(sql: string, callback?: (err: Error | null) => void): this;
|
||||
|
||||
exec(sql: string, callback?: (err: Error | null) => void): this;
|
||||
|
||||
close(callback?: (err: Error | null) => void): void;
|
||||
}
|
||||
|
||||
export class Statement {
|
||||
bind(params: any[]): this;
|
||||
reset(): this;
|
||||
finalize(callback?: (err: Error | null) => void): void;
|
||||
}
|
||||
|
||||
export function verbose(): any;
|
||||
|
||||
export class Database {
|
||||
constructor(filename: string, mode?: number, callback?: (err: Error | null) => void);
|
||||
constructor(filename: string, callback?: (err: Error | null) => void);
|
||||
}
|
||||
}
|
||||
549
index.ts
Normal file
549
index.ts
Normal file
@@ -0,0 +1,549 @@
|
||||
#!/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 sqlite3 from "sqlite3";
|
||||
|
||||
// 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];
|
||||
|
||||
// Create a resource base URL for SQLite
|
||||
const resourceBaseUrl = new URL(`sqlite:///${databasePath}`);
|
||||
const SCHEMA_PATH = "schema";
|
||||
|
||||
// Initialize SQLite database connection
|
||||
let db: sqlite3.Database;
|
||||
|
||||
function initDatabase(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db = new sqlite3.Database(databasePath, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to run a query and get all results
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to run a query that doesn't return results
|
||||
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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to run multiple statements
|
||||
function dbExec(query: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.exec(query, (err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// List all available database resources (tables)
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
// Query to get all table names
|
||||
const result = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
|
||||
return {
|
||||
resources: result.map((row) => ({
|
||||
uri: new URL(`${row.name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
|
||||
mimeType: "application/json",
|
||||
name: `"${row.name}" database schema`,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Get schema information for a specific table
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const resourceUrl = new URL(request.params.uri);
|
||||
|
||||
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: request.params.uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(result.map((column) => ({
|
||||
column_name: column.name,
|
||||
data_type: column.type
|
||||
})), null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// List available tools
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
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"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Helper function to convert data to CSV format
|
||||
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;
|
||||
}
|
||||
|
||||
// Handle tool calls
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
switch (request.params.name) {
|
||||
case "read_query": {
|
||||
const query = request.params.arguments?.query as string;
|
||||
|
||||
if (!query.trim().toLowerCase().startsWith("select")) {
|
||||
throw new Error("Only SELECT queries are allowed with read_query");
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await dbAll(query);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "write_query": {
|
||||
const query = request.params.arguments?.query as string;
|
||||
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");
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await dbRun(query);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ affected_rows: result.changes }, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "create_table": {
|
||||
const query = request.params.arguments?.query as string;
|
||||
|
||||
if (!query.trim().toLowerCase().startsWith("create table")) {
|
||||
throw new Error("Only CREATE TABLE statements are allowed");
|
||||
}
|
||||
|
||||
try {
|
||||
await dbExec(query);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ success: true, message: "Table created successfully" }, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "alter_table": {
|
||||
const query = request.params.arguments?.query as string;
|
||||
|
||||
if (!query.trim().toLowerCase().startsWith("alter table")) {
|
||||
throw new Error("Only ALTER TABLE statements are allowed");
|
||||
}
|
||||
|
||||
try {
|
||||
await dbExec(query);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ success: true, message: "Table altered successfully" }, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`SQL Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "drop_table": {
|
||||
const tableName = request.params.arguments?.table_name as string;
|
||||
const confirm = request.params.arguments?.confirm as boolean;
|
||||
|
||||
if (!tableName) {
|
||||
throw new Error("Table name is required");
|
||||
}
|
||||
|
||||
if (!confirm) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
message: "Safety confirmation required. Set confirm=true to proceed with dropping the table."
|
||||
}, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 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 {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ success: true, message: `Table '${tableName}' dropped successfully` }, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error dropping table: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "export_query": {
|
||||
const query = request.params.arguments?.query as string;
|
||||
const format = request.params.arguments?.format as string;
|
||||
|
||||
if (!query.trim().toLowerCase().startsWith("select")) {
|
||||
throw new Error("Only SELECT queries are allowed with export_query");
|
||||
}
|
||||
|
||||
try {
|
||||
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 {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Unsupported export format. Use 'csv' or 'json'");
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Export Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "list_tables": {
|
||||
try {
|
||||
const tables = await dbAll(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(tables.map((t) => t.name), null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error listing tables: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "describe_table": {
|
||||
const tableName = request.params.arguments?.table_name as string;
|
||||
|
||||
if (!tableName) {
|
||||
throw new Error("Table name is required");
|
||||
}
|
||||
|
||||
try {
|
||||
// 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 {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(columns.map((col) => ({
|
||||
name: col.name,
|
||||
type: col.type,
|
||||
notnull: !!col.notnull,
|
||||
default_value: col.dflt_value,
|
||||
primary_key: !!col.pk
|
||||
})), null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error describing table: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
case "append_insight": {
|
||||
const insight = request.params.arguments?.insight as string;
|
||||
|
||||
if (!insight) {
|
||||
throw new Error("Insight text is required");
|
||||
}
|
||||
|
||||
try {
|
||||
// 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 {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ success: true, message: "Insight added" }, null, 2)
|
||||
}],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error adding insight: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
async function runServer() {
|
||||
try {
|
||||
await initDatabase();
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runServer().catch(console.error);
|
||||
2656
package-lock.json
generated
Normal file
2656
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@executeautomation/database-server",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP server for interacting with SQLite databases by ExecuteAutomation",
|
||||
"license": "MIT",
|
||||
"author": "ExecuteAutomation (https://executeautomation.com)",
|
||||
"homepage": "https://executeautomation.com",
|
||||
"bugs": "https://github.com/executeautomation/database-server/issues",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"ea-database-server": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && shx chmod +x dist/*.js",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsc && node dist/index.js",
|
||||
"example": "node examples/example.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.9.0",
|
||||
"sqlite3": "5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sqlite3": "5.1.0",
|
||||
"shx": "0.4.0",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
202
readme.md
Normal file
202
readme.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# ExecuteAutomation Database Server
|
||||
|
||||
A Model Context Protocol server that provides access to SQLite databases. This server enables LLMs like Claude to inspect database schemas and execute SQL queries.
|
||||
|
||||
## Components
|
||||
|
||||
### Tools
|
||||
|
||||
The server offers nine core tools:
|
||||
|
||||
#### Query Tools
|
||||
- **read_query**
|
||||
- Execute SELECT queries to read data from the database
|
||||
- Input:
|
||||
- `query` (string): The SELECT SQL query to execute
|
||||
- Returns: Query results as array of objects
|
||||
|
||||
- **write_query**
|
||||
- Execute INSERT, UPDATE, or DELETE queries
|
||||
- Input:
|
||||
- `query` (string): The SQL modification query
|
||||
- Returns: `{ affected_rows: number }`
|
||||
|
||||
#### Schema Management Tools
|
||||
- **create_table**
|
||||
- Create new tables in the database
|
||||
- Input:
|
||||
- `query` (string): CREATE TABLE SQL statement
|
||||
- Returns: Confirmation of table creation
|
||||
|
||||
- **alter_table**
|
||||
- Modify existing table schema (add columns, rename tables, etc.)
|
||||
- Input:
|
||||
- `query` (string): ALTER TABLE SQL statement
|
||||
- Returns: Confirmation of table alteration
|
||||
|
||||
- **drop_table**
|
||||
- Remove a table from the database with safety confirmation
|
||||
- Input:
|
||||
- `table_name` (string): Name of the table to drop
|
||||
- `confirm` (boolean): Safety confirmation flag (must be true to actually drop)
|
||||
- Returns: Confirmation message or safety warning
|
||||
|
||||
#### Schema Information Tools
|
||||
- **list_tables**
|
||||
- Get a list of all tables in the database
|
||||
- No input required
|
||||
- Returns: Array of table names
|
||||
|
||||
- **describe_table**
|
||||
- View schema information for a specific table
|
||||
- Input:
|
||||
- `table_name` (string): Name of table to describe
|
||||
- Returns: Array of column definitions with names and types
|
||||
|
||||
#### Data Export Tools
|
||||
- **export_query**
|
||||
- Export query results to various formats
|
||||
- Input:
|
||||
- `query` (string): The SELECT SQL query to execute
|
||||
- `format` (string): Output format - either "csv" or "json"
|
||||
- Returns: Query results formatted as CSV or JSON
|
||||
|
||||
#### Insights
|
||||
- **append_insight**
|
||||
- Add a business insight to the memo
|
||||
- Input:
|
||||
- `insight` (string): Business insight discovered from data analysis
|
||||
- Returns: Confirmation message
|
||||
|
||||
### Resources
|
||||
|
||||
The server provides schema information for each table in the database:
|
||||
|
||||
- **Table Schemas** (`sqlite:///<path>/<table>/schema`)
|
||||
- JSON schema information for each table
|
||||
- Includes column names and data types
|
||||
- Automatically discovered from database metadata
|
||||
|
||||
## Usage with Claude Desktop
|
||||
|
||||
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
|
||||
|
||||
### Docker
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"database": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"-i",
|
||||
"--rm",
|
||||
"executeautomation/database-server",
|
||||
"/path/to/database.db"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NPX (Published Package)
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"database": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@executeautomation/database-server",
|
||||
"/path/to/database.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
For local development and debugging, you can point directly to your compiled JavaScript file:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"database": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/path/to/your/project/dist/index.js",
|
||||
"/path/to/your/database.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example with full paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"database": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/Users/username/projects/database-server/dist/index.js",
|
||||
"/Users/username/databases/mydata.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Claude Desktop config file is typically located at:
|
||||
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
- Linux: `~/.config/Claude/claude_desktop_config.json`
|
||||
|
||||
Replace all paths with your actual file paths.
|
||||
|
||||
## Building and Development
|
||||
|
||||
### Quick Build
|
||||
|
||||
Use the included build script to install dependencies and build the project:
|
||||
|
||||
```sh
|
||||
chmod +x build.sh
|
||||
./build.sh
|
||||
```
|
||||
|
||||
### Manual Build Steps
|
||||
|
||||
```sh
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build the TypeScript
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Docker Build
|
||||
|
||||
```sh
|
||||
docker build -t executeautomation/database-server -f Dockerfile .
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter connection issues:
|
||||
|
||||
1. Make sure the database file path is correct and accessible
|
||||
2. Verify that the compiled JavaScript file exists at the specified path
|
||||
3. Check the Claude Desktop logs for detailed error messages
|
||||
4. Restart Claude Desktop after making configuration changes
|
||||
|
||||
## License
|
||||
|
||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License.
|
||||
|
||||
## About ExecuteAutomation
|
||||
|
||||
This server is maintained by ExecuteAutomation. Visit [executeautomation.com](https://executeautomation.com) for more tools and resources.
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"declaration": false
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user