@hyqf98/easy_db_mcp_server 1.0.0 → 2.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.
@@ -1,4 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
+ import fs from 'fs';
3
+ import { expandTilde, formatFileSize, ensureDirectoryExists, getDefaultFilePath } from '../utils/file.js';
2
4
  export class SQLiteAdapter {
3
5
  db;
4
6
  config;
@@ -66,4 +68,225 @@ export class SQLiteAdapter {
66
68
  this.db = undefined;
67
69
  }
68
70
  }
71
+ async executeTransaction(queries) {
72
+ if (!this.db)
73
+ throw new Error('Not connected');
74
+ const results = [];
75
+ let totalAffectedRows = 0;
76
+ this.db.exec('BEGIN TRANSACTION');
77
+ try {
78
+ for (const query of queries) {
79
+ const trimmed = query.trim().toUpperCase();
80
+ if (trimmed.startsWith('SELECT')) {
81
+ const rows = this.db.prepare(query).all();
82
+ results.push({ rows, rowCount: rows.length });
83
+ }
84
+ else {
85
+ const result = this.db.prepare(query).run();
86
+ totalAffectedRows += result.changes;
87
+ results.push({ affectedRows: result.changes });
88
+ }
89
+ }
90
+ this.db.exec('COMMIT');
91
+ return {
92
+ success: true,
93
+ affectedRows: totalAffectedRows,
94
+ results,
95
+ };
96
+ }
97
+ catch (error) {
98
+ this.db.exec('ROLLBACK');
99
+ throw error;
100
+ }
101
+ }
102
+ async batchInsert(table, data) {
103
+ if (!this.db)
104
+ throw new Error('Not connected');
105
+ if (data.length === 0) {
106
+ return { insertedRows: 0, duplicateRows: 0 };
107
+ }
108
+ const columns = Object.keys(data[0]);
109
+ const columnList = columns.join(', ');
110
+ const placeholders = columns.map(() => '?').join(', ');
111
+ const query = `INSERT INTO ${table} (${columnList}) VALUES (${placeholders})`;
112
+ const stmt = this.db.prepare(query);
113
+ let insertedRows = 0;
114
+ let duplicateRows = 0;
115
+ this.db.exec('BEGIN TRANSACTION');
116
+ try {
117
+ for (const row of data) {
118
+ try {
119
+ const values = columns.map((col) => row[col]);
120
+ stmt.run(values);
121
+ insertedRows++;
122
+ }
123
+ catch (error) {
124
+ if (error.code === 'SQLITE_CONSTRAINT') {
125
+ duplicateRows++;
126
+ }
127
+ else {
128
+ throw error;
129
+ }
130
+ }
131
+ }
132
+ this.db.exec('COMMIT');
133
+ return { insertedRows, duplicateRows };
134
+ }
135
+ catch (error) {
136
+ this.db.exec('ROLLBACK');
137
+ throw error;
138
+ }
139
+ }
140
+ async batchUpdate(table, updates) {
141
+ if (!this.db)
142
+ throw new Error('Not connected');
143
+ const setClause = Object.entries(updates.set)
144
+ .map(([key, value]) => `${key} = ${this.escapeValue(value)}`)
145
+ .join(', ');
146
+ const query = `UPDATE ${table} SET ${setClause} WHERE ${updates.where}`;
147
+ const result = this.db.prepare(query).run();
148
+ return { affectedRows: result.changes };
149
+ }
150
+ escapeValue(value) {
151
+ if (value === null)
152
+ return 'NULL';
153
+ if (typeof value === 'number')
154
+ return String(value);
155
+ if (typeof value === 'boolean')
156
+ return value ? '1' : '0';
157
+ return `'${String(value).replace(/'/g, "''")}'`;
158
+ }
159
+ async exportData(table, format, filePath, options) {
160
+ if (!this.db)
161
+ throw new Error('Not connected');
162
+ let query = `SELECT * FROM ${table}`;
163
+ if (options?.where) {
164
+ query += ` WHERE ${options.where}`;
165
+ }
166
+ if (options?.limit) {
167
+ query += ` LIMIT ${options.limit}`;
168
+ }
169
+ const data = this.db.prepare(query).all();
170
+ const targetPath = filePath
171
+ ? expandTilde(filePath)
172
+ : getDefaultFilePath(table, format);
173
+ ensureDirectoryExists(targetPath);
174
+ let content;
175
+ if (format === 'json') {
176
+ content = JSON.stringify(data, null, 2);
177
+ }
178
+ else {
179
+ if (data.length === 0) {
180
+ content = '';
181
+ }
182
+ else {
183
+ const headers = Object.keys(data[0]);
184
+ const csvRows = [
185
+ headers.join(','),
186
+ ...data.map((row) => headers.map((h) => {
187
+ const val = row[h];
188
+ if (val === null)
189
+ return '';
190
+ if (typeof val === 'string')
191
+ return '"' + val.replace(/"/g, '""') + '"';
192
+ return String(val);
193
+ }).join(',')),
194
+ ];
195
+ content = csvRows.join('\n');
196
+ }
197
+ }
198
+ fs.writeFileSync(targetPath, content, 'utf-8');
199
+ const stats = fs.statSync(targetPath);
200
+ return {
201
+ success: true,
202
+ filePath: targetPath,
203
+ rowCount: data.length,
204
+ fileSize: formatFileSize(stats.size),
205
+ };
206
+ }
207
+ async createTable(table, columns) {
208
+ if (!this.db)
209
+ throw new Error('Not connected');
210
+ const columnDefs = columns.map((col) => {
211
+ let def = `${col.name} ${col.type}`;
212
+ if (col.nullable === false)
213
+ def += ' NOT NULL';
214
+ if (col.primaryKey)
215
+ def += ' PRIMARY KEY';
216
+ if (col.defaultValue !== undefined) {
217
+ def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
218
+ }
219
+ return def;
220
+ });
221
+ const query = `CREATE TABLE ${table} (${columnDefs.join(', ')})`;
222
+ this.db.exec(query);
223
+ return { success: true, tableName: table };
224
+ }
225
+ async dropTable(table, ifExists = false) {
226
+ if (!this.db)
227
+ throw new Error('Not connected');
228
+ const query = ifExists
229
+ ? `DROP TABLE IF EXISTS ${table}`
230
+ : `DROP TABLE ${table}`;
231
+ this.db.exec(query);
232
+ return { success: true, tableName: table };
233
+ }
234
+ async getTableStats(table) {
235
+ if (!this.db)
236
+ throw new Error('Not connected');
237
+ // Get row count
238
+ const countResult = this.db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get();
239
+ const rowCount = countResult.count;
240
+ // Get column count
241
+ const colResult = this.db.prepare(`PRAGMA table_info(${table})`).all();
242
+ const columns = colResult.length;
243
+ // Get indexes
244
+ const indexResult = this.db.prepare(`PRAGMA index_list(${table})`).all();
245
+ const indexes = indexResult.map((row) => row.name);
246
+ // Get table size (SQLite doesn't have a direct way, estimate from page count)
247
+ const pragmaResult = this.db.prepare(`PRAGMA page_count`).get();
248
+ const pageSizeResult = this.db.prepare(`PRAGMA page_size`).get();
249
+ const sizeBytes = pragmaResult.page_count * pageSizeResult.page_size;
250
+ const size = formatFileSize(sizeBytes);
251
+ return {
252
+ tableName: table,
253
+ rowCount,
254
+ columns,
255
+ indexes,
256
+ size,
257
+ };
258
+ }
259
+ async previewData(table, page = 1, pageSize = 50, orderBy) {
260
+ if (!this.db)
261
+ throw new Error('Not connected');
262
+ // Get total row count
263
+ const countResult = this.db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get();
264
+ const totalRows = countResult.count;
265
+ const totalPages = Math.ceil(totalRows / pageSize);
266
+ const offset = (page - 1) * pageSize;
267
+ // Build query
268
+ let query = `SELECT * FROM ${table}`;
269
+ if (orderBy) {
270
+ query += ` ORDER BY ${orderBy}`;
271
+ }
272
+ query += ` LIMIT ${pageSize} OFFSET ${offset}`;
273
+ const rows = this.db.prepare(query).all();
274
+ return {
275
+ rows,
276
+ currentPage: page,
277
+ totalPages,
278
+ totalRows,
279
+ };
280
+ }
281
+ async sampleData(table, count = 10) {
282
+ if (!this.db)
283
+ throw new Error('Not connected');
284
+ const rows = this.db
285
+ .prepare(`SELECT * FROM ${table} ORDER BY RANDOM() LIMIT ${count}`)
286
+ .all();
287
+ return {
288
+ rows,
289
+ sampleCount: rows.length,
290
+ };
291
+ }
69
292
  }
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { z } from 'zod';
5
5
  import { loadConfig } from './config.js';
6
6
  import { createAdapter } from './database/factory.js';
7
- // Create server instance
8
- const server = new Server({
7
+ // Create server instance with new API
8
+ const server = new McpServer({
9
9
  name: 'hyqf98@easy_db_mcp_server',
10
- version: '1.0.0',
10
+ version: '2.0.0',
11
11
  }, {
12
12
  capabilities: {
13
13
  tools: {},
@@ -15,191 +15,260 @@ const server = new Server({
15
15
  });
16
16
  // Load configuration and create adapter
17
17
  let adapter;
18
+ let cachedConfig;
18
19
  try {
19
- const config = loadConfig();
20
- adapter = createAdapter(config);
20
+ cachedConfig = loadConfig();
21
+ adapter = createAdapter(cachedConfig);
21
22
  await adapter.connect();
22
- console.error(`Connected to ${config.type} database`);
23
+ console.error(`Connected to ${cachedConfig.type} database`);
23
24
  }
24
25
  catch (error) {
25
26
  console.error('Failed to connect to database:', error);
26
27
  process.exit(1);
27
28
  }
28
- // List available tools
29
- server.setRequestHandler(ListToolsRequestSchema, async () => {
29
+ // Helper function to validate database parameter
30
+ const getDatabase = (paramDb) => {
31
+ if (paramDb) {
32
+ return paramDb;
33
+ }
34
+ if (cachedConfig.database) {
35
+ return cachedConfig.database;
36
+ }
37
+ throw new Error('Database name is required. Either set EASYDB_DATABASE environment variable or pass the database parameter in the tool call.');
38
+ };
39
+ // Register existing tools with new API
40
+ server.registerTool('list_tables', {
41
+ title: 'List Tables',
42
+ description: 'List all tables in the database. If EASYDB_DATABASE is not set, the database parameter must be provided.',
43
+ inputSchema: {
44
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set, otherwise required)'),
45
+ },
46
+ }, async ({ database }) => {
47
+ const dbName = getDatabase(database);
48
+ const tables = await adapter.listTables(dbName);
49
+ return {
50
+ content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }],
51
+ };
52
+ });
53
+ server.registerTool('describe_table', {
54
+ title: 'Describe Table',
55
+ description: 'Get the structure of a table including columns, types, and constraints. If EASYDB_DATABASE is not set, the database parameter must be provided.',
56
+ inputSchema: {
57
+ table: z.string().describe('Table name'),
58
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set, otherwise required)'),
59
+ },
60
+ }, async ({ table, database }) => {
61
+ const dbName = getDatabase(database);
62
+ const columns = await adapter.describeTable(table, dbName);
63
+ return {
64
+ content: [{ type: 'text', text: JSON.stringify(columns, null, 2) }],
65
+ };
66
+ });
67
+ server.registerTool('execute_query', {
68
+ title: 'Execute Query',
69
+ description: 'Execute a SELECT query (read-only). If EASYDB_DATABASE is not set, the database parameter must be provided.',
70
+ inputSchema: {
71
+ sql: z.string().describe('SQL SELECT query to execute'),
72
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set, otherwise required)'),
73
+ },
74
+ }, async ({ sql, database }) => {
75
+ const dbName = getDatabase(database);
76
+ const result = await adapter.executeQuery(sql, dbName);
77
+ return {
78
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
79
+ };
80
+ });
81
+ server.registerTool('execute_sql', {
82
+ title: 'Execute SQL',
83
+ description: 'Execute any SQL statement (requires EASYDB_ALLOW_WRITE=true). If EASYDB_DATABASE is not set, the database parameter must be provided.',
84
+ inputSchema: {
85
+ sql: z.string().describe('SQL statement to execute (INSERT, UPDATE, DELETE, DDL, etc.)'),
86
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set, otherwise required)'),
87
+ },
88
+ }, async ({ sql, database }) => {
89
+ if (!cachedConfig.allowWrite) {
90
+ throw new Error('SQL execution requires EASYDB_ALLOW_WRITE=true for safety. Please enable this environment variable if you want to allow write operations.');
91
+ }
92
+ // Improved DDL detection using regex to handle various formats
93
+ const trimmed = sql.trim().toUpperCase();
94
+ const ddlPattern = /^(CREATE|DROP|ALTER|TRUNCATE|RENAME)\s+(TABLE|INDEX|DATABASE|SCHEMA|VIEW|PROCEDURE|FUNCTION|TRIGGER)/;
95
+ const isDDL = ddlPattern.test(trimmed);
96
+ if (isDDL && !cachedConfig.allowDDL) {
97
+ throw new Error('DDL statements require EASYDB_ALLOW_DDL=true for safety. Please enable this environment variable if you want to allow DDL operations.');
98
+ }
99
+ const dbName = getDatabase(database);
100
+ const result = await adapter.executeSQL(sql, dbName);
101
+ return {
102
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
103
+ };
104
+ });
105
+ server.registerTool('execute_transaction', {
106
+ title: 'Execute Transaction',
107
+ description: 'Execute multiple SQL statements as a transaction. All queries will succeed or all will be rolled back.',
108
+ inputSchema: {
109
+ sql: z.array(z.string()).max(100).describe('Array of SQL statements to execute in transaction (max 100)'),
110
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
111
+ },
112
+ }, async ({ sql, database }) => {
113
+ if (!cachedConfig.allowWrite) {
114
+ throw new Error('Transaction execution requires EASYDB_ALLOW_WRITE=true');
115
+ }
116
+ const dbName = getDatabase(database);
117
+ const result = await adapter.executeTransaction(sql, dbName);
118
+ return {
119
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
120
+ };
121
+ });
122
+ server.registerTool('batch_insert', {
123
+ title: 'Batch Insert',
124
+ description: 'Insert multiple rows into a table efficiently',
125
+ inputSchema: {
126
+ table: z.string().describe('Table name'),
127
+ data: z.array(z.record(z.string(), z.any())).describe('Array of row data objects'),
128
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
129
+ },
130
+ }, async ({ table, data, database }) => {
131
+ if (!cachedConfig.allowWrite) {
132
+ throw new Error('Batch insert requires EASYDB_ALLOW_WRITE=true');
133
+ }
134
+ const dbName = getDatabase(database);
135
+ const result = await adapter.batchInsert(table, data, dbName);
30
136
  return {
31
- tools: [
32
- {
33
- name: 'list_tables',
34
- description: 'List all tables in the database. If EASYDB_DATABASE is not set, the database parameter must be provided.',
35
- inputSchema: {
36
- type: 'object',
37
- properties: {
38
- database: {
39
- type: 'string',
40
- description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
41
- },
42
- },
43
- },
44
- },
45
- {
46
- name: 'describe_table',
47
- description: 'Get the structure of a table including columns, types, and constraints. If EASYDB_DATABASE is not set, the database parameter must be provided.',
48
- inputSchema: {
49
- type: 'object',
50
- properties: {
51
- table: {
52
- type: 'string',
53
- description: 'Table name',
54
- },
55
- database: {
56
- type: 'string',
57
- description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
58
- },
59
- },
60
- required: ['table'],
61
- },
62
- },
63
- {
64
- name: 'execute_query',
65
- description: 'Execute a SELECT query (read-only). If EASYDB_DATABASE is not set, the database parameter must be provided.',
66
- inputSchema: {
67
- type: 'object',
68
- properties: {
69
- sql: {
70
- type: 'string',
71
- description: 'SQL SELECT query to execute',
72
- },
73
- database: {
74
- type: 'string',
75
- description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
76
- },
77
- },
78
- required: ['sql'],
79
- },
80
- },
81
- {
82
- name: 'execute_sql',
83
- description: 'Execute any SQL statement (requires EASYDB_ALLOW_WRITE=true). If EASYDB_DATABASE is not set, the database parameter must be provided.',
84
- inputSchema: {
85
- type: 'object',
86
- properties: {
87
- sql: {
88
- type: 'string',
89
- description: 'SQL statement to execute (INSERT, UPDATE, DELETE, DDL, etc.)',
90
- },
91
- database: {
92
- type: 'string',
93
- description: 'Database name (optional if EASYDB_DATABASE is set, otherwise required)',
94
- },
95
- },
96
- required: ['sql'],
97
- },
98
- },
99
- ],
137
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
100
138
  };
101
139
  });
102
- // Handle tool calls
103
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
104
- const { name, arguments: args } = request.params;
105
- const config = loadConfig();
106
- // Helper function to validate database parameter
107
- const getDatabase = (paramDb) => {
108
- if (paramDb) {
109
- return paramDb;
110
- }
111
- if (config.database) {
112
- return config.database;
113
- }
114
- throw new Error('Database name is required. Either set EASYDB_DATABASE environment variable or pass the database parameter in the tool call.');
140
+ server.registerTool('batch_update', {
141
+ title: 'Batch Update',
142
+ description: 'Update multiple rows in a table based on conditions',
143
+ inputSchema: {
144
+ table: z.string().describe('Table name'),
145
+ updates: z.object({
146
+ set: z.record(z.string(), z.any()).describe('Column values to set'),
147
+ where: z.string().describe('WHERE clause condition'),
148
+ }),
149
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
150
+ },
151
+ }, async ({ table, updates, database }) => {
152
+ if (!cachedConfig.allowWrite) {
153
+ throw new Error('Batch update requires EASYDB_ALLOW_WRITE=true');
154
+ }
155
+ const dbName = getDatabase(database);
156
+ const result = await adapter.batchUpdate(table, updates, dbName);
157
+ return {
158
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
159
+ };
160
+ });
161
+ server.registerTool('export_data', {
162
+ title: 'Export Data',
163
+ description: 'Export table data to JSON or CSV file. Default saves to ~/table_name.json',
164
+ inputSchema: {
165
+ table: z.string().describe('Table name to export'),
166
+ format: z.enum(['json', 'csv']).describe('Export format'),
167
+ filePath: z.string().optional().describe('Optional file path (default: ~/table_name.format)'),
168
+ limit: z.number().optional().describe('Optional row limit'),
169
+ where: z.string().optional().describe('Optional WHERE clause'),
170
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
171
+ },
172
+ }, async ({ table, format, filePath, limit, where, database }) => {
173
+ const dbName = getDatabase(database);
174
+ const result = await adapter.exportData(table, format, filePath, { limit, where }, dbName);
175
+ return {
176
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
115
177
  };
116
- try {
117
- switch (name) {
118
- case 'list_tables': {
119
- const database = getDatabase(args?.database);
120
- const tables = await adapter.listTables(database);
121
- return {
122
- content: [
123
- {
124
- type: 'text',
125
- text: JSON.stringify(tables, null, 2),
126
- },
127
- ],
128
- };
129
- }
130
- case 'describe_table': {
131
- const database = getDatabase(args?.database);
132
- const columns = await adapter.describeTable(args?.table, database);
133
- return {
134
- content: [
135
- {
136
- type: 'text',
137
- text: JSON.stringify(columns, null, 2),
138
- },
139
- ],
140
- };
141
- }
142
- case 'execute_query': {
143
- const database = getDatabase(args?.database);
144
- const result = await adapter.executeQuery(args?.sql, database);
145
- return {
146
- content: [
147
- {
148
- type: 'text',
149
- text: JSON.stringify(result, null, 2),
150
- },
151
- ],
152
- };
153
- }
154
- case 'execute_sql': {
155
- if (!config.allowWrite) {
156
- throw new Error('SQL execution requires EASYDB_ALLOW_WRITE=true for safety. Please enable this environment variable if you want to allow write operations.');
157
- }
158
- const sql = args?.sql;
159
- const trimmed = sql.trim().toUpperCase();
160
- // Check for DDL statements
161
- const isDDL = trimmed.startsWith('CREATE ') ||
162
- trimmed.startsWith('DROP ') ||
163
- trimmed.startsWith('ALTER ') ||
164
- trimmed.startsWith('TRUNCATE ');
165
- if (isDDL && !config.allowDDL) {
166
- throw new Error('DDL statements require EASYDB_ALLOW_DDL=true for safety. Please enable this environment variable if you want to allow DDL operations.');
167
- }
168
- const database = getDatabase(args?.database);
169
- const result = await adapter.executeSQL(sql, database);
170
- return {
171
- content: [
172
- {
173
- type: 'text',
174
- text: JSON.stringify(result, null, 2),
175
- },
176
- ],
177
- };
178
- }
179
- default:
180
- throw new Error(`Unknown tool: ${name}`);
181
- }
178
+ });
179
+ server.registerTool('create_table', {
180
+ title: 'Create Table',
181
+ description: 'Create a new table with specified columns',
182
+ inputSchema: {
183
+ table: z.string().describe('Table name'),
184
+ columns: z.array(z.object({
185
+ name: z.string().describe('Column name'),
186
+ type: z.string().describe('Column data type'),
187
+ nullable: z.boolean().optional().describe('Whether column allows NULL'),
188
+ primaryKey: z.boolean().optional().describe('Whether column is primary key'),
189
+ defaultValue: z.any().optional().describe('Default value'),
190
+ })).describe('Column definitions'),
191
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
192
+ },
193
+ }, async ({ table, columns, database }) => {
194
+ if (!cachedConfig.allowDDL) {
195
+ throw new Error('Create table requires EASYDB_ALLOW_DDL=true');
182
196
  }
183
- catch (error) {
184
- return {
185
- content: [
186
- {
187
- type: 'text',
188
- text: JSON.stringify({
189
- success: false,
190
- error: error instanceof Error ? error.message : String(error),
191
- }, null, 2),
192
- },
193
- ],
194
- isError: true,
195
- };
197
+ const dbName = getDatabase(database);
198
+ const result = await adapter.createTable(table, columns, dbName);
199
+ return {
200
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
201
+ };
202
+ });
203
+ server.registerTool('drop_table', {
204
+ title: 'Drop Table',
205
+ description: 'Delete a table from the database',
206
+ inputSchema: {
207
+ table: z.string().describe('Table name to drop'),
208
+ ifExists: z.boolean().optional().describe('Use IF EXISTS to avoid error if table does not exist'),
209
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
210
+ },
211
+ }, async ({ table, ifExists, database }) => {
212
+ if (!cachedConfig.allowDDL) {
213
+ throw new Error('Drop table requires EASYDB_ALLOW_DDL=true');
196
214
  }
215
+ const dbName = getDatabase(database);
216
+ const result = await adapter.dropTable(table, ifExists, dbName);
217
+ return {
218
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
219
+ };
220
+ });
221
+ server.registerTool('get_table_stats', {
222
+ title: 'Get Table Statistics',
223
+ description: 'Get detailed statistics about a table including row count, columns, indexes, and size',
224
+ inputSchema: {
225
+ table: z.string().describe('Table name'),
226
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
227
+ },
228
+ }, async ({ table, database }) => {
229
+ const dbName = getDatabase(database);
230
+ const result = await adapter.getTableStats(table, dbName);
231
+ return {
232
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
233
+ };
234
+ });
235
+ server.registerTool('preview_data', {
236
+ title: 'Preview Data',
237
+ description: 'Preview table data with pagination',
238
+ inputSchema: {
239
+ table: z.string().describe('Table name'),
240
+ page: z.number().optional().describe('Page number (default: 1)'),
241
+ pageSize: z.number().optional().describe('Rows per page (default: 50)'),
242
+ orderBy: z.string().optional().describe('Order by column name'),
243
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
244
+ },
245
+ }, async ({ table, page = 1, pageSize = 50, orderBy, database }) => {
246
+ const dbName = getDatabase(database);
247
+ const result = await adapter.previewData(table, page, pageSize, orderBy, dbName);
248
+ return {
249
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
250
+ };
251
+ });
252
+ server.registerTool('sample_data', {
253
+ title: 'Sample Data',
254
+ description: 'Get a random sample of rows from a table',
255
+ inputSchema: {
256
+ table: z.string().describe('Table name'),
257
+ count: z.number().optional().describe('Number of rows to sample (default: 10)'),
258
+ database: z.string().optional().describe('Database name (optional if EASYDB_DATABASE is set)'),
259
+ },
260
+ }, async ({ table, count = 10, database }) => {
261
+ const dbName = getDatabase(database);
262
+ const result = await adapter.sampleData(table, count, dbName);
263
+ return {
264
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
265
+ };
197
266
  });
198
267
  // Start server
199
268
  async function main() {
200
269
  const transport = new StdioServerTransport();
201
270
  await server.connect(transport);
202
- console.error('EasyDB MCP Server running');
271
+ console.error('EasyDB MCP Server v2.0.0 running');
203
272
  }
204
273
  main().catch((error) => {
205
274
  console.error('Server error:', error);
@@ -0,0 +1,4 @@
1
+ export declare function expandTilde(filePath: string): string;
2
+ export declare function formatFileSize(bytes: number): string;
3
+ export declare function ensureDirectoryExists(filePath: string): void;
4
+ export declare function getDefaultFilePath(baseName: string, extension: string): string;