@berthojoris/mcp-mysql-server 1.5.0 → 1.6.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,91 @@
1
+ import { SecurityLayer } from '../security/securityLayer';
2
+ export declare class ViewTools {
3
+ private db;
4
+ private security;
5
+ constructor(security: SecurityLayer);
6
+ /**
7
+ * Validate database access - ensures only the connected database can be accessed
8
+ */
9
+ private validateDatabaseAccess;
10
+ /**
11
+ * List all views in the current database
12
+ */
13
+ listViews(params: {
14
+ database?: string;
15
+ }): Promise<{
16
+ status: string;
17
+ data?: any[];
18
+ error?: string;
19
+ queryLog?: string;
20
+ }>;
21
+ /**
22
+ * Get detailed information about a specific view
23
+ */
24
+ getViewInfo(params: {
25
+ view_name: string;
26
+ database?: string;
27
+ }): Promise<{
28
+ status: string;
29
+ data?: any;
30
+ error?: string;
31
+ queryLog?: string;
32
+ }>;
33
+ /**
34
+ * Create a new view
35
+ */
36
+ createView(params: {
37
+ view_name: string;
38
+ definition: string;
39
+ or_replace?: boolean;
40
+ algorithm?: 'UNDEFINED' | 'MERGE' | 'TEMPTABLE';
41
+ security?: 'DEFINER' | 'INVOKER';
42
+ check_option?: 'CASCADED' | 'LOCAL';
43
+ database?: string;
44
+ }): Promise<{
45
+ status: string;
46
+ data?: any;
47
+ error?: string;
48
+ queryLog?: string;
49
+ }>;
50
+ /**
51
+ * Alter an existing view
52
+ */
53
+ alterView(params: {
54
+ view_name: string;
55
+ definition: string;
56
+ algorithm?: 'UNDEFINED' | 'MERGE' | 'TEMPTABLE';
57
+ security?: 'DEFINER' | 'INVOKER';
58
+ check_option?: 'CASCADED' | 'LOCAL';
59
+ database?: string;
60
+ }): Promise<{
61
+ status: string;
62
+ data?: any;
63
+ error?: string;
64
+ queryLog?: string;
65
+ }>;
66
+ /**
67
+ * Drop a view
68
+ */
69
+ dropView(params: {
70
+ view_name: string;
71
+ if_exists?: boolean;
72
+ database?: string;
73
+ }): Promise<{
74
+ status: string;
75
+ message?: string;
76
+ error?: string;
77
+ queryLog?: string;
78
+ }>;
79
+ /**
80
+ * Show the CREATE statement for a view
81
+ */
82
+ showCreateView(params: {
83
+ view_name: string;
84
+ database?: string;
85
+ }): Promise<{
86
+ status: string;
87
+ data?: any;
88
+ error?: string;
89
+ queryLog?: string;
90
+ }>;
91
+ }
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ViewTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const config_1 = require("../config/config");
9
+ class ViewTools {
10
+ constructor(security) {
11
+ this.db = connection_1.default.getInstance();
12
+ this.security = security;
13
+ }
14
+ /**
15
+ * Validate database access - ensures only the connected database can be accessed
16
+ */
17
+ validateDatabaseAccess(requestedDatabase) {
18
+ const connectedDatabase = config_1.dbConfig.database;
19
+ if (!connectedDatabase) {
20
+ return {
21
+ valid: false,
22
+ database: '',
23
+ error: 'No database specified in connection string. Cannot access any database.'
24
+ };
25
+ }
26
+ if (!requestedDatabase) {
27
+ return {
28
+ valid: true,
29
+ database: connectedDatabase
30
+ };
31
+ }
32
+ if (requestedDatabase !== connectedDatabase) {
33
+ return {
34
+ valid: false,
35
+ database: '',
36
+ error: `Access denied. You can only access the connected database '${connectedDatabase}'. Requested database '${requestedDatabase}' is not allowed.`
37
+ };
38
+ }
39
+ return {
40
+ valid: true,
41
+ database: connectedDatabase
42
+ };
43
+ }
44
+ /**
45
+ * List all views in the current database
46
+ */
47
+ async listViews(params) {
48
+ try {
49
+ const dbValidation = this.validateDatabaseAccess(params?.database);
50
+ if (!dbValidation.valid) {
51
+ return { status: 'error', error: dbValidation.error };
52
+ }
53
+ const database = dbValidation.database;
54
+ const query = `
55
+ SELECT
56
+ TABLE_NAME as view_name,
57
+ VIEW_DEFINITION as definition,
58
+ CHECK_OPTION as check_option,
59
+ IS_UPDATABLE as is_updatable,
60
+ DEFINER as definer,
61
+ SECURITY_TYPE as security_type,
62
+ CHARACTER_SET_CLIENT as charset,
63
+ COLLATION_CONNECTION as collation
64
+ FROM INFORMATION_SCHEMA.VIEWS
65
+ WHERE TABLE_SCHEMA = ?
66
+ ORDER BY TABLE_NAME
67
+ `;
68
+ const results = await this.db.query(query, [database]);
69
+ return {
70
+ status: 'success',
71
+ data: results,
72
+ queryLog: this.db.getFormattedQueryLogs(1)
73
+ };
74
+ }
75
+ catch (error) {
76
+ return {
77
+ status: 'error',
78
+ error: error.message,
79
+ queryLog: this.db.getFormattedQueryLogs(1)
80
+ };
81
+ }
82
+ }
83
+ /**
84
+ * Get detailed information about a specific view
85
+ */
86
+ async getViewInfo(params) {
87
+ try {
88
+ const dbValidation = this.validateDatabaseAccess(params?.database);
89
+ if (!dbValidation.valid) {
90
+ return { status: 'error', error: dbValidation.error };
91
+ }
92
+ const { view_name } = params;
93
+ const database = dbValidation.database;
94
+ // Validate view name
95
+ const identifierValidation = this.security.validateIdentifier(view_name);
96
+ if (!identifierValidation.valid) {
97
+ return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
98
+ }
99
+ // Get view information
100
+ const viewQuery = `
101
+ SELECT
102
+ TABLE_NAME as view_name,
103
+ VIEW_DEFINITION as definition,
104
+ CHECK_OPTION as check_option,
105
+ IS_UPDATABLE as is_updatable,
106
+ DEFINER as definer,
107
+ SECURITY_TYPE as security_type,
108
+ CHARACTER_SET_CLIENT as charset,
109
+ COLLATION_CONNECTION as collation
110
+ FROM INFORMATION_SCHEMA.VIEWS
111
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
112
+ `;
113
+ // Get view columns
114
+ const columnsQuery = `
115
+ SELECT
116
+ COLUMN_NAME as column_name,
117
+ DATA_TYPE as data_type,
118
+ COLUMN_TYPE as column_type,
119
+ IS_NULLABLE as is_nullable,
120
+ COLUMN_DEFAULT as column_default,
121
+ ORDINAL_POSITION as position
122
+ FROM INFORMATION_SCHEMA.COLUMNS
123
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
124
+ ORDER BY ORDINAL_POSITION
125
+ `;
126
+ const [viewInfo, columns] = await Promise.all([
127
+ this.db.query(viewQuery, [database, view_name]),
128
+ this.db.query(columnsQuery, [database, view_name])
129
+ ]);
130
+ if (viewInfo.length === 0) {
131
+ return {
132
+ status: 'error',
133
+ error: `View '${view_name}' not found in database '${database}'`,
134
+ queryLog: this.db.getFormattedQueryLogs(2)
135
+ };
136
+ }
137
+ return {
138
+ status: 'success',
139
+ data: {
140
+ ...viewInfo[0],
141
+ columns: columns
142
+ },
143
+ queryLog: this.db.getFormattedQueryLogs(2)
144
+ };
145
+ }
146
+ catch (error) {
147
+ return {
148
+ status: 'error',
149
+ error: error.message,
150
+ queryLog: this.db.getFormattedQueryLogs(2)
151
+ };
152
+ }
153
+ }
154
+ /**
155
+ * Create a new view
156
+ */
157
+ async createView(params) {
158
+ try {
159
+ const dbValidation = this.validateDatabaseAccess(params?.database);
160
+ if (!dbValidation.valid) {
161
+ return { status: 'error', error: dbValidation.error };
162
+ }
163
+ const { view_name, definition, or_replace = false, algorithm, security, check_option } = params;
164
+ const database = dbValidation.database;
165
+ // Validate view name
166
+ const identifierValidation = this.security.validateIdentifier(view_name);
167
+ if (!identifierValidation.valid) {
168
+ return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
169
+ }
170
+ // Validate that definition is a SELECT statement
171
+ const trimmedDef = definition.trim().toUpperCase();
172
+ if (!trimmedDef.startsWith('SELECT')) {
173
+ return { status: 'error', error: 'View definition must be a SELECT statement' };
174
+ }
175
+ // Build CREATE VIEW statement
176
+ let createQuery = or_replace ? 'CREATE OR REPLACE ' : 'CREATE ';
177
+ if (algorithm) {
178
+ createQuery += `ALGORITHM = ${algorithm} `;
179
+ }
180
+ if (security) {
181
+ createQuery += `SQL SECURITY ${security} `;
182
+ }
183
+ createQuery += `VIEW \`${database}\`.\`${view_name}\` AS ${definition}`;
184
+ if (check_option) {
185
+ createQuery += ` WITH ${check_option} CHECK OPTION`;
186
+ }
187
+ await this.db.query(createQuery);
188
+ return {
189
+ status: 'success',
190
+ data: {
191
+ message: `View '${view_name}' created successfully`,
192
+ view_name,
193
+ database
194
+ },
195
+ queryLog: this.db.getFormattedQueryLogs(1)
196
+ };
197
+ }
198
+ catch (error) {
199
+ return {
200
+ status: 'error',
201
+ error: error.message,
202
+ queryLog: this.db.getFormattedQueryLogs(1)
203
+ };
204
+ }
205
+ }
206
+ /**
207
+ * Alter an existing view
208
+ */
209
+ async alterView(params) {
210
+ try {
211
+ const dbValidation = this.validateDatabaseAccess(params?.database);
212
+ if (!dbValidation.valid) {
213
+ return { status: 'error', error: dbValidation.error };
214
+ }
215
+ const { view_name, definition, algorithm, security, check_option } = params;
216
+ const database = dbValidation.database;
217
+ // Validate view name
218
+ const identifierValidation = this.security.validateIdentifier(view_name);
219
+ if (!identifierValidation.valid) {
220
+ return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
221
+ }
222
+ // Validate that definition is a SELECT statement
223
+ const trimmedDef = definition.trim().toUpperCase();
224
+ if (!trimmedDef.startsWith('SELECT')) {
225
+ return { status: 'error', error: 'View definition must be a SELECT statement' };
226
+ }
227
+ // Build ALTER VIEW statement
228
+ let alterQuery = 'ALTER ';
229
+ if (algorithm) {
230
+ alterQuery += `ALGORITHM = ${algorithm} `;
231
+ }
232
+ if (security) {
233
+ alterQuery += `SQL SECURITY ${security} `;
234
+ }
235
+ alterQuery += `VIEW \`${database}\`.\`${view_name}\` AS ${definition}`;
236
+ if (check_option) {
237
+ alterQuery += ` WITH ${check_option} CHECK OPTION`;
238
+ }
239
+ await this.db.query(alterQuery);
240
+ return {
241
+ status: 'success',
242
+ data: {
243
+ message: `View '${view_name}' altered successfully`,
244
+ view_name,
245
+ database
246
+ },
247
+ queryLog: this.db.getFormattedQueryLogs(1)
248
+ };
249
+ }
250
+ catch (error) {
251
+ return {
252
+ status: 'error',
253
+ error: error.message,
254
+ queryLog: this.db.getFormattedQueryLogs(1)
255
+ };
256
+ }
257
+ }
258
+ /**
259
+ * Drop a view
260
+ */
261
+ async dropView(params) {
262
+ try {
263
+ const dbValidation = this.validateDatabaseAccess(params?.database);
264
+ if (!dbValidation.valid) {
265
+ return { status: 'error', error: dbValidation.error };
266
+ }
267
+ const { view_name, if_exists = false } = params;
268
+ const database = dbValidation.database;
269
+ // Validate view name
270
+ const identifierValidation = this.security.validateIdentifier(view_name);
271
+ if (!identifierValidation.valid) {
272
+ return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
273
+ }
274
+ const dropQuery = `DROP VIEW ${if_exists ? 'IF EXISTS' : ''} \`${database}\`.\`${view_name}\``;
275
+ await this.db.query(dropQuery);
276
+ return {
277
+ status: 'success',
278
+ message: `View '${view_name}' dropped successfully`,
279
+ queryLog: this.db.getFormattedQueryLogs(1)
280
+ };
281
+ }
282
+ catch (error) {
283
+ return {
284
+ status: 'error',
285
+ error: error.message,
286
+ queryLog: this.db.getFormattedQueryLogs(1)
287
+ };
288
+ }
289
+ }
290
+ /**
291
+ * Show the CREATE statement for a view
292
+ */
293
+ async showCreateView(params) {
294
+ try {
295
+ const dbValidation = this.validateDatabaseAccess(params?.database);
296
+ if (!dbValidation.valid) {
297
+ return { status: 'error', error: dbValidation.error };
298
+ }
299
+ const { view_name } = params;
300
+ const database = dbValidation.database;
301
+ // Validate view name
302
+ const identifierValidation = this.security.validateIdentifier(view_name);
303
+ if (!identifierValidation.valid) {
304
+ return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
305
+ }
306
+ const query = `SHOW CREATE VIEW \`${database}\`.\`${view_name}\``;
307
+ const results = await this.db.query(query);
308
+ if (results.length === 0) {
309
+ return {
310
+ status: 'error',
311
+ error: `View '${view_name}' not found`,
312
+ queryLog: this.db.getFormattedQueryLogs(1)
313
+ };
314
+ }
315
+ return {
316
+ status: 'success',
317
+ data: results[0],
318
+ queryLog: this.db.getFormattedQueryLogs(1)
319
+ };
320
+ }
321
+ catch (error) {
322
+ return {
323
+ status: 'error',
324
+ error: error.message,
325
+ queryLog: this.db.getFormattedQueryLogs(1)
326
+ };
327
+ }
328
+ }
329
+ }
330
+ exports.ViewTools = ViewTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@berthojoris/mcp-mysql-server",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions and data export capabilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",