@hyqf98/easy_db_mcp_server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +58 -0
- package/dist/database/base.d.ts +44 -0
- package/dist/database/base.js +1 -0
- package/dist/database/factory.d.ts +3 -0
- package/dist/database/factory.js +15 -0
- package/dist/database/mysql.d.ts +15 -0
- package/dist/database/mysql.js +96 -0
- package/dist/database/postgresql.d.ts +15 -0
- package/dist/database/postgresql.js +97 -0
- package/dist/database/sqlite.d.ts +15 -0
- package/dist/database/sqlite.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +213 -0
- package/package.json +40 -0
- package/src/config.ts +75 -0
- package/src/database/base.ts +50 -0
- package/src/database/factory.ts +18 -0
- package/src/database/mysql.ts +124 -0
- package/src/database/postgresql.ts +126 -0
- package/src/database/sqlite.ts +93 -0
- package/src/index.ts +256 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseAdapter,
|
|
4
|
+
TableInfo,
|
|
5
|
+
TableColumn,
|
|
6
|
+
QueryResult,
|
|
7
|
+
} from './base.js';
|
|
8
|
+
import type { DatabaseConfig } from '../config.js';
|
|
9
|
+
|
|
10
|
+
export class SQLiteAdapter implements DatabaseAdapter {
|
|
11
|
+
private db?: Database.Database;
|
|
12
|
+
private config: DatabaseConfig;
|
|
13
|
+
|
|
14
|
+
constructor(config: DatabaseConfig) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async connect(): Promise<void> {
|
|
19
|
+
if (!this.config.database) {
|
|
20
|
+
throw new Error('Database file path required for SQLite');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.db = new Database(this.config.database);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async listTables(): Promise<TableInfo[]> {
|
|
27
|
+
if (!this.db) throw new Error('Not connected');
|
|
28
|
+
|
|
29
|
+
const rows = this.db
|
|
30
|
+
.prepare(
|
|
31
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
|
|
32
|
+
)
|
|
33
|
+
.all() as any[];
|
|
34
|
+
|
|
35
|
+
return rows.map((row: any) => ({
|
|
36
|
+
name: row.name,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async describeTable(table: string): Promise<TableColumn[]> {
|
|
41
|
+
if (!this.db) throw new Error('Not connected');
|
|
42
|
+
|
|
43
|
+
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all() as any[];
|
|
44
|
+
|
|
45
|
+
return rows.map((row: any) => ({
|
|
46
|
+
name: row.name,
|
|
47
|
+
type: row.type,
|
|
48
|
+
nullable: row.notnull === 0,
|
|
49
|
+
defaultValue: row.dflt_value,
|
|
50
|
+
primaryKey: row.pk > 0,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async executeQuery(sql: string): Promise<QueryResult> {
|
|
55
|
+
if (!this.db) throw new Error('Not connected');
|
|
56
|
+
|
|
57
|
+
const trimmed = sql.trim().toUpperCase();
|
|
58
|
+
if (!trimmed.startsWith('SELECT')) {
|
|
59
|
+
throw new Error('Only SELECT queries allowed in executeQuery');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rows = this.db.prepare(sql).all() as Record<string, unknown>[];
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
rows,
|
|
66
|
+
rowCount: rows.length,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async executeSQL(sql: string): Promise<QueryResult | { affectedRows: number }> {
|
|
71
|
+
if (!this.db) throw new Error('Not connected');
|
|
72
|
+
|
|
73
|
+
const trimmed = sql.trim().toUpperCase();
|
|
74
|
+
|
|
75
|
+
if (trimmed.startsWith('SELECT')) {
|
|
76
|
+
const rows = this.db.prepare(sql).all() as Record<string, unknown>[];
|
|
77
|
+
return {
|
|
78
|
+
rows,
|
|
79
|
+
rowCount: rows.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = this.db.prepare(sql).run();
|
|
84
|
+
return { affectedRows: result.changes };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async close(): Promise<void> {
|
|
88
|
+
if (this.db) {
|
|
89
|
+
this.db.close();
|
|
90
|
+
this.db = undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { loadConfig } from './config.js';
|
|
9
|
+
import { createAdapter } from './database/factory.js';
|
|
10
|
+
import type { DatabaseAdapter } from './database/base.js';
|
|
11
|
+
|
|
12
|
+
// Create server instance
|
|
13
|
+
const server = new Server(
|
|
14
|
+
{
|
|
15
|
+
name: 'hyqf98@easy_db_mcp_server',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
capabilities: {
|
|
20
|
+
tools: {},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Load configuration and create adapter
|
|
26
|
+
let adapter: DatabaseAdapter;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
adapter = createAdapter(config);
|
|
31
|
+
await adapter.connect();
|
|
32
|
+
console.error(`Connected to ${config.type} database`);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Failed to connect to database:', error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// List available tools
|
|
39
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
40
|
+
return {
|
|
41
|
+
tools: [
|
|
42
|
+
{
|
|
43
|
+
name: 'list_tables',
|
|
44
|
+
description: 'List all tables in the database. If EASYDB_DATABASE is not set, the database parameter must be provided.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
database: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'describe_table',
|
|
57
|
+
description: 'Get the structure of a table including columns, types, and constraints. If EASYDB_DATABASE is not set, the database parameter must be provided.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
table: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Table name',
|
|
64
|
+
},
|
|
65
|
+
database: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['table'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'execute_query',
|
|
75
|
+
description: 'Execute a SELECT query (read-only). If EASYDB_DATABASE is not set, the database parameter must be provided.',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
sql: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
description: 'SQL SELECT query to execute',
|
|
82
|
+
},
|
|
83
|
+
database: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['sql'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'execute_sql',
|
|
93
|
+
description: 'Execute any SQL statement (requires EASYDB_ALLOW_WRITE=true). If EASYDB_DATABASE is not set, the database parameter must be provided.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
sql: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'SQL statement to execute (INSERT, UPDATE, DELETE, DDL, etc.)',
|
|
100
|
+
},
|
|
101
|
+
database: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ['sql'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Handle tool calls
|
|
114
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
115
|
+
const { name, arguments: args } = request.params;
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
|
|
118
|
+
// Helper function to validate database parameter
|
|
119
|
+
const getDatabase = (paramDb?: string): string => {
|
|
120
|
+
if (paramDb) {
|
|
121
|
+
return paramDb;
|
|
122
|
+
}
|
|
123
|
+
if (config.database) {
|
|
124
|
+
return config.database;
|
|
125
|
+
}
|
|
126
|
+
throw new Error(
|
|
127
|
+
'Database name is required. Either set EASYDB_DATABASE environment variable or pass the database parameter in the tool call.'
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
switch (name) {
|
|
133
|
+
case 'list_tables': {
|
|
134
|
+
const database = getDatabase(args?.database as string | undefined);
|
|
135
|
+
const tables = await adapter.listTables(database);
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: 'text',
|
|
140
|
+
text: JSON.stringify(tables, null, 2),
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case 'describe_table': {
|
|
147
|
+
const database = getDatabase(args?.database as string | undefined);
|
|
148
|
+
const columns = await adapter.describeTable(
|
|
149
|
+
args?.table as string,
|
|
150
|
+
database
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: 'text',
|
|
156
|
+
text: JSON.stringify(columns, null, 2),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'execute_query': {
|
|
163
|
+
const database = getDatabase(args?.database as string | undefined);
|
|
164
|
+
const result = await adapter.executeQuery(
|
|
165
|
+
args?.sql as string,
|
|
166
|
+
database
|
|
167
|
+
);
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: 'text',
|
|
172
|
+
text: JSON.stringify(result, null, 2),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'execute_sql': {
|
|
179
|
+
if (!config.allowWrite) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
'SQL execution requires EASYDB_ALLOW_WRITE=true for safety. Please enable this environment variable if you want to allow write operations.'
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const sql = args?.sql as string;
|
|
186
|
+
const trimmed = sql.trim().toUpperCase();
|
|
187
|
+
|
|
188
|
+
// Check for DDL statements
|
|
189
|
+
const isDDL =
|
|
190
|
+
trimmed.startsWith('CREATE ') ||
|
|
191
|
+
trimmed.startsWith('DROP ') ||
|
|
192
|
+
trimmed.startsWith('ALTER ') ||
|
|
193
|
+
trimmed.startsWith('TRUNCATE ');
|
|
194
|
+
|
|
195
|
+
if (isDDL && !config.allowDDL) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
'DDL statements require EASYDB_ALLOW_DDL=true for safety. Please enable this environment variable if you want to allow DDL operations.'
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const database = getDatabase(args?.database as string | undefined);
|
|
202
|
+
const result = await adapter.executeSQL(
|
|
203
|
+
sql,
|
|
204
|
+
database
|
|
205
|
+
);
|
|
206
|
+
return {
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: JSON.stringify(result, null, 2),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: JSON.stringify(
|
|
225
|
+
{
|
|
226
|
+
success: false,
|
|
227
|
+
error: error instanceof Error ? error.message : String(error),
|
|
228
|
+
},
|
|
229
|
+
null,
|
|
230
|
+
2
|
|
231
|
+
),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
isError: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Start server
|
|
240
|
+
async function main() {
|
|
241
|
+
const transport = new StdioServerTransport();
|
|
242
|
+
await server.connect(transport);
|
|
243
|
+
console.error('EasyDB MCP Server running');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
main().catch((error) => {
|
|
247
|
+
console.error('Server error:', error);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Cleanup on exit
|
|
252
|
+
process.on('SIGINT', async () => {
|
|
253
|
+
console.error('\\nShutting down...');
|
|
254
|
+
await adapter.close();
|
|
255
|
+
process.exit(0);
|
|
256
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|