@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.
@@ -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
+ }