mcp-database-inspector 2.0.1

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.
Files changed (105) hide show
  1. package/README.md +197 -0
  2. package/dist/database/connection.d.ts +13 -0
  3. package/dist/database/connection.d.ts.map +1 -0
  4. package/dist/database/connection.js +155 -0
  5. package/dist/database/connection.js.map +1 -0
  6. package/dist/database/manager.d.ts +28 -0
  7. package/dist/database/manager.d.ts.map +1 -0
  8. package/dist/database/manager.js +621 -0
  9. package/dist/database/manager.js.map +1 -0
  10. package/dist/database/postgres-connection.d.ts +10 -0
  11. package/dist/database/postgres-connection.d.ts.map +1 -0
  12. package/dist/database/postgres-connection.js +113 -0
  13. package/dist/database/postgres-connection.js.map +1 -0
  14. package/dist/database/types.d.ts +84 -0
  15. package/dist/database/types.d.ts.map +1 -0
  16. package/dist/database/types.js +6 -0
  17. package/dist/database/types.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +120 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server.d.ts +14 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +186 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/test-defaults.d.ts +2 -0
  27. package/dist/test-defaults.d.ts.map +1 -0
  28. package/dist/test-defaults.js +57 -0
  29. package/dist/test-defaults.js.map +1 -0
  30. package/dist/tools/analyze-query.d.ts +27 -0
  31. package/dist/tools/analyze-query.d.ts.map +1 -0
  32. package/dist/tools/analyze-query.js +71 -0
  33. package/dist/tools/analyze-query.js.map +1 -0
  34. package/dist/tools/execute-query.d.ts +33 -0
  35. package/dist/tools/execute-query.d.ts.map +1 -0
  36. package/dist/tools/execute-query.js +57 -0
  37. package/dist/tools/execute-query.js.map +1 -0
  38. package/dist/tools/get-foreign-keys.d.ts +38 -0
  39. package/dist/tools/get-foreign-keys.d.ts.map +1 -0
  40. package/dist/tools/get-foreign-keys.js +391 -0
  41. package/dist/tools/get-foreign-keys.js.map +1 -0
  42. package/dist/tools/get-indexes.d.ts +38 -0
  43. package/dist/tools/get-indexes.d.ts.map +1 -0
  44. package/dist/tools/get-indexes.js +472 -0
  45. package/dist/tools/get-indexes.js.map +1 -0
  46. package/dist/tools/information-schema-query.d.ts +33 -0
  47. package/dist/tools/information-schema-query.d.ts.map +1 -0
  48. package/dist/tools/information-schema-query.js +76 -0
  49. package/dist/tools/information-schema-query.js.map +1 -0
  50. package/dist/tools/inspect-table.d.ts +38 -0
  51. package/dist/tools/inspect-table.d.ts.map +1 -0
  52. package/dist/tools/inspect-table.js +351 -0
  53. package/dist/tools/inspect-table.js.map +1 -0
  54. package/dist/tools/list-databases.d.ts +14 -0
  55. package/dist/tools/list-databases.d.ts.map +1 -0
  56. package/dist/tools/list-databases.js +83 -0
  57. package/dist/tools/list-databases.js.map +1 -0
  58. package/dist/tools/list-tables.d.ts +19 -0
  59. package/dist/tools/list-tables.d.ts.map +1 -0
  60. package/dist/tools/list-tables.js +130 -0
  61. package/dist/tools/list-tables.js.map +1 -0
  62. package/dist/utils/errors.d.ts +32 -0
  63. package/dist/utils/errors.d.ts.map +1 -0
  64. package/dist/utils/errors.js +98 -0
  65. package/dist/utils/errors.js.map +1 -0
  66. package/dist/utils/logger.d.ts +28 -0
  67. package/dist/utils/logger.d.ts.map +1 -0
  68. package/dist/utils/logger.js +132 -0
  69. package/dist/utils/logger.js.map +1 -0
  70. package/dist/validators/input-validator.d.ts +76 -0
  71. package/dist/validators/input-validator.d.ts.map +1 -0
  72. package/dist/validators/input-validator.js +295 -0
  73. package/dist/validators/input-validator.js.map +1 -0
  74. package/dist/validators/query-validator.d.ts +19 -0
  75. package/dist/validators/query-validator.d.ts.map +1 -0
  76. package/dist/validators/query-validator.js +229 -0
  77. package/dist/validators/query-validator.js.map +1 -0
  78. package/enhanced_sql_prompt.md +324 -0
  79. package/examples/claude-config.json +23 -0
  80. package/examples/roo-config.json +16 -0
  81. package/package.json +42 -0
  82. package/src/database/connection.ts +165 -0
  83. package/src/database/manager.ts +682 -0
  84. package/src/database/postgres-connection.ts +123 -0
  85. package/src/database/types.ts +93 -0
  86. package/src/index.ts +136 -0
  87. package/src/server.ts +254 -0
  88. package/src/test-defaults.ts +63 -0
  89. package/src/tools/analyze-query.test.ts +100 -0
  90. package/src/tools/analyze-query.ts +112 -0
  91. package/src/tools/execute-query.ts +91 -0
  92. package/src/tools/get-foreign-keys.test.ts +51 -0
  93. package/src/tools/get-foreign-keys.ts +488 -0
  94. package/src/tools/get-indexes.test.ts +51 -0
  95. package/src/tools/get-indexes.ts +570 -0
  96. package/src/tools/information-schema-query.ts +125 -0
  97. package/src/tools/inspect-table.test.ts +59 -0
  98. package/src/tools/inspect-table.ts +440 -0
  99. package/src/tools/list-databases.ts +119 -0
  100. package/src/tools/list-tables.ts +181 -0
  101. package/src/utils/errors.ts +103 -0
  102. package/src/utils/logger.ts +158 -0
  103. package/src/validators/input-validator.ts +318 -0
  104. package/src/validators/query-validator.ts +267 -0
  105. package/tsconfig.json +30 -0
@@ -0,0 +1,123 @@
1
+ import pg from 'pg';
2
+ import { URL } from 'url';
3
+ import { DatabaseConnectionOptions, QueryResult, DatabaseType } from './types.js';
4
+ import { Logger } from '../utils/logger.js';
5
+ import { DatabaseError } from '../utils/errors.js';
6
+
7
+ export class PostgresConnection {
8
+ static parseConnectionUrl(url: string): DatabaseConnectionOptions {
9
+ try {
10
+ const parsedUrl = new URL(url);
11
+
12
+ if (parsedUrl.protocol !== 'postgresql:' && parsedUrl.protocol !== 'postgres:') {
13
+ throw new DatabaseError(`Unsupported protocol: ${parsedUrl.protocol}. Only 'postgresql:' or 'postgres:' is supported.`);
14
+ }
15
+
16
+ const options: DatabaseConnectionOptions = {
17
+ type: DatabaseType.PostgreSQL,
18
+ host: parsedUrl.hostname,
19
+ port: parsedUrl.port ? parseInt(parsedUrl.port) : 5432,
20
+ user: parsedUrl.username,
21
+ password: parsedUrl.password,
22
+ database: parsedUrl.pathname.slice(1), // Remove leading slash
23
+ reconnect: true
24
+ };
25
+
26
+ // Parse SSL settings from query parameters
27
+ const searchParams = parsedUrl.searchParams;
28
+ if (searchParams.has('ssl')) {
29
+ const sslValue = searchParams.get('ssl');
30
+ if (sslValue === 'true' || sslValue === '1') {
31
+ options.ssl = true;
32
+ } else if (sslValue === 'false' || sslValue === '0') {
33
+ options.ssl = false;
34
+ } else if (sslValue === 'no-verify') {
35
+ options.ssl = { rejectUnauthorized: false };
36
+ }
37
+ }
38
+
39
+ return options;
40
+ } catch (error) {
41
+ throw new DatabaseError(`Invalid connection URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
42
+ }
43
+ }
44
+
45
+ static async createClient(options: DatabaseConnectionOptions): Promise<pg.Client> {
46
+ try {
47
+ Logger.info(`Connecting to PostgreSQL database at ${options.host}:${options.port}/${options.database}`);
48
+
49
+ const clientConfig: pg.ClientConfig = {
50
+ host: options.host,
51
+ port: options.port,
52
+ user: options.user,
53
+ password: options.password,
54
+ database: options.database,
55
+ };
56
+
57
+ if (options.ssl) {
58
+ clientConfig.ssl = options.ssl;
59
+ }
60
+
61
+ const client = new pg.Client(clientConfig);
62
+ await client.connect();
63
+
64
+ // Test the connection
65
+ await client.query('SELECT 1');
66
+
67
+ Logger.info(`Successfully connected to PostgreSQL database: ${options.database}`);
68
+ return client;
69
+ } catch (error) {
70
+ Logger.error(`Failed to connect to PostgreSQL database: ${error instanceof Error ? error.message : 'Unknown error'}`);
71
+ throw new DatabaseError(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
72
+ }
73
+ }
74
+
75
+ static async executeQuery(client: pg.Client, query: string, params?: any[]): Promise<QueryResult> {
76
+ try {
77
+ Logger.debug(`Executing PostgreSQL query: ${query.substring(0, 100)}${query.length > 100 ? '...' : ''}`);
78
+
79
+ // PostgreSQL uses $1, $2 for parameters, but if we receive ? from MySQL style queries
80
+ // we might need translation, but for now we expect raw compatibility or manual handling
81
+ const res = await client.query(query, params);
82
+
83
+ const result: QueryResult = {
84
+ rows: res.rows,
85
+ fields: res.fields.map((f: any) => ({ name: f.name, type: f.dataTypeID })),
86
+ };
87
+
88
+ Logger.debug(`Query executed successfully, returned ${result.rows.length} rows`);
89
+ return result;
90
+ } catch (error) {
91
+ Logger.error(
92
+ `PostgreSQL query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}\n` +
93
+ `Full SQL: ${query}\n` +
94
+ `Params: ${JSON.stringify(params)}`
95
+ );
96
+ throw new DatabaseError(`Query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
97
+ }
98
+ }
99
+
100
+ static async closeConnection(client: pg.Client): Promise<void> {
101
+ try {
102
+ await client.end();
103
+ Logger.info('PostgreSQL connection closed successfully');
104
+ } catch (error) {
105
+ Logger.warn(`Error closing PostgreSQL connection: ${error instanceof Error ? error.message : 'Unknown error'}`);
106
+ }
107
+ }
108
+
109
+ static async testConnection(options: DatabaseConnectionOptions): Promise<boolean> {
110
+ let client: pg.Client | null = null;
111
+ try {
112
+ client = await this.createClient(options);
113
+ return true;
114
+ } catch (error) {
115
+ Logger.warn(`PostgreSQL connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
116
+ return false;
117
+ } finally {
118
+ if (client) {
119
+ await this.closeConnection(client);
120
+ }
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,93 @@
1
+ export enum DatabaseType {
2
+ MySQL = 'mysql',
3
+ PostgreSQL = 'postgresql'
4
+ }
5
+
6
+ export interface DatabaseConfig {
7
+ name: string;
8
+ url: string;
9
+ type: DatabaseType;
10
+ connection: any | null; // Can be mysql.Connection or pg.Client
11
+ lastUsed: Date;
12
+ host: string;
13
+ port: number;
14
+ username: string;
15
+ password?: string;
16
+ database: string;
17
+ ssl?: boolean | any;
18
+ }
19
+
20
+ export interface DatabaseInfo {
21
+ name: string;
22
+ type: DatabaseType;
23
+ connected: boolean;
24
+ lastUsed: Date;
25
+ host: string;
26
+ database: string;
27
+ }
28
+
29
+ export interface TableInfo {
30
+ tableName: string;
31
+ tableType: string;
32
+ engine?: string;
33
+ tableRows?: number;
34
+ tableComment?: string;
35
+ }
36
+
37
+ export interface ColumnInfo {
38
+ columnName: string;
39
+ dataType: string;
40
+ isNullable: string;
41
+ columnDefault?: any;
42
+ isPrimaryKey: boolean;
43
+ isAutoIncrement: boolean;
44
+ columnComment?: string;
45
+ characterMaximumLength?: number;
46
+ numericPrecision?: number;
47
+ numericScale?: number;
48
+ }
49
+
50
+ export interface ForeignKeyInfo {
51
+ constraintName: string;
52
+ tableName: string;
53
+ columnName: string;
54
+ referencedTableName: string;
55
+ referencedColumnName: string;
56
+ updateRule?: string;
57
+ deleteRule?: string;
58
+ }
59
+
60
+ export interface IndexInfo {
61
+ tableName: string;
62
+ indexName: string;
63
+ columnName: string;
64
+ nonUnique: boolean;
65
+ indexType?: string;
66
+ cardinality?: number;
67
+ subPart?: number;
68
+ nullable: boolean;
69
+ isPrimary?: boolean;
70
+ }
71
+
72
+ export interface QueryResult {
73
+ rows: any[];
74
+ fields: any[];
75
+ }
76
+
77
+ export interface ValidationResult {
78
+ isValid: boolean;
79
+ error?: string;
80
+ warnings?: string[];
81
+ }
82
+
83
+ export interface DatabaseConnectionOptions {
84
+ type: DatabaseType;
85
+ host: string;
86
+ port: number;
87
+ user: string;
88
+ password: string;
89
+ database: string;
90
+ ssl?: boolean | object;
91
+ // timeout and acquireTimeout are not valid for Connection configs and should not be used
92
+ reconnect?: boolean;
93
+ }
package/src/index.ts ADDED
@@ -0,0 +1,136 @@
1
+ import { DatabaseInspectorServer } from './server.js';
2
+ import { Logger } from './utils/logger.js';
3
+ import { InputValidator } from './validators/input-validator.js';
4
+
5
+ async function main() {
6
+ try {
7
+ // Parse command line arguments
8
+ const args = process.argv.slice(2);
9
+
10
+ if (args.length === 0) {
11
+ console.error(`
12
+ Usage: mcp-database-inspector <database_url1> [database_url2] [database_url3] ...
13
+
14
+ Examples:
15
+ # Single MySQL database
16
+ mcp-database-inspector "mysql://user:password@localhost:3306/mydb"
17
+
18
+ # Single PostgreSQL database
19
+ mcp-database-inspector "postgresql://user:password@localhost:5432/mydb"
20
+
21
+ # Mixed databases
22
+ mcp-database-inspector \\
23
+ "mysql://user:pass@mysql-host:3306/orders" \\
24
+ "postgresql://user:pass@pg-host:5432/analytics"
25
+
26
+ # With SSL
27
+ mcp-database-inspector "mysql://user:pass@localhost:3306/db?ssl=true"
28
+
29
+ Environment Variables:
30
+ LOG_LEVEL - Set logging level (error, warn, info, debug, trace)
31
+ Default: info
32
+
33
+ Database URL Format:
34
+ mysql://username:password@hostname:port/database?options
35
+ postgresql://username:password@hostname:port/database?options
36
+
37
+ Supported Options:
38
+ - ssl=true/false - Enable/disable SSL connection
39
+ - timeout=30 - Connection timeout in seconds (default: 30)
40
+ `);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Set log level from environment
45
+ const logLevel = process.env.LOG_LEVEL?.toLowerCase();
46
+ if (logLevel && ['error', 'warn', 'info', 'debug', 'trace'].includes(logLevel)) {
47
+ Logger.setLogLevel(logLevel as any);
48
+ }
49
+
50
+ Logger.info('MCP Database Inspector starting...');
51
+ Logger.info(`Log level: ${Logger.getLogLevel()}`);
52
+
53
+ // Validate connection URLs
54
+ const connectionUrls: string[] = [];
55
+ for (const url of args) {
56
+ Logger.debug(`Validating connection URL: ${InputValidator.sanitizeForLogging(url)}`);
57
+
58
+ const validation = InputValidator.validateConnectionUrl(url);
59
+ if (!validation.isValid) {
60
+ Logger.error(`Invalid connection URL: ${validation.error}`);
61
+ console.error(`Error: Invalid connection URL - ${validation.error}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ connectionUrls.push(url);
66
+ Logger.debug(`Connection URL validated successfully`);
67
+ }
68
+
69
+ Logger.info(`Validated ${connectionUrls.length} connection URL(s)`);
70
+
71
+ // Create and initialize server
72
+ const server = new DatabaseInspectorServer();
73
+
74
+ // Set up graceful shutdown
75
+ let isShuttingDown = false;
76
+ const shutdownHandler = async () => {
77
+ if (isShuttingDown) return;
78
+ isShuttingDown = true;
79
+
80
+ Logger.info('Shutting down...');
81
+ try {
82
+ await server.shutdown();
83
+ } catch (error) {
84
+ Logger.error('Error during shutdown:', error);
85
+ }
86
+ process.exit(0);
87
+ };
88
+
89
+ process.on('SIGINT', shutdownHandler);
90
+ process.on('SIGTERM', shutdownHandler);
91
+
92
+ // Initialize with database connections
93
+ Logger.info('Initializing database connections...');
94
+ await server.initialize(connectionUrls);
95
+
96
+ // Get server info for logging
97
+ const serverInfo = await server.getServerInfo();
98
+ Logger.info('Server initialization complete', {
99
+ connectedDatabases: serverInfo.connectedDatabases,
100
+ databases: serverInfo.databases.map((db: any) => db.name),
101
+ status: serverInfo.status
102
+ });
103
+
104
+ // Start the server
105
+ await server.run();
106
+
107
+ // This point should not be reached as the server runs indefinitely
108
+ Logger.warn('Server.run() returned unexpectedly');
109
+
110
+ } catch (error) {
111
+ Logger.error('Fatal error during startup:', error);
112
+ console.error(`Fatal error: ${error instanceof Error ? error.message : 'Unknown error'}`);
113
+ process.exit(1);
114
+ }
115
+ }
116
+
117
+ // Handle unhandled promise rejections
118
+ process.on('unhandledRejection', (reason, promise) => {
119
+ Logger.error('Unhandled Promise Rejection:', { promise, reason });
120
+ console.error('Unhandled Promise Rejection:', reason);
121
+ process.exit(1);
122
+ });
123
+
124
+ // Handle uncaught exceptions
125
+ process.on('uncaughtException', (error) => {
126
+ Logger.error('Uncaught Exception:', error);
127
+ console.error('Uncaught Exception:', error.message);
128
+ process.exit(1);
129
+ });
130
+
131
+ // Start the application
132
+ main().catch((error) => {
133
+ Logger.error('Error in main function:', error);
134
+ console.error('Startup error:', error.message);
135
+ process.exit(1);
136
+ });
package/src/server.ts ADDED
@@ -0,0 +1,254 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js';
7
+
8
+ import { DatabaseManager } from './database/manager.js';
9
+ import { Logger } from './utils/logger.js';
10
+ import { createErrorResponse, ToolError } from './utils/errors.js';
11
+
12
+ // Import tool handlers
13
+ import {
14
+ listDatabasesToolDefinition,
15
+ handleListDatabases
16
+ } from './tools/list-databases.js';
17
+ import {
18
+ listTablesToolDefinition,
19
+ handleListTables
20
+ } from './tools/list-tables.js';
21
+ import {
22
+ inspectTableToolDefinition,
23
+ handleInspectTable
24
+ } from './tools/inspect-table.js';
25
+ import {
26
+ getForeignKeysToolDefinition,
27
+ handleGetForeignKeys
28
+ } from './tools/get-foreign-keys.js';
29
+ import {
30
+ getIndexesToolDefinition,
31
+ handleGetIndexes
32
+ } from './tools/get-indexes.js';
33
+
34
+ import {
35
+ informationSchemaQueryToolDefinition,
36
+ handleInformationSchemaQuery
37
+ } from './tools/information-schema-query.js';
38
+
39
+ import {
40
+ executeQueryToolDefinition,
41
+ handleExecuteQuery
42
+ } from './tools/execute-query.js';
43
+
44
+ import {
45
+ analyzeQueryToolDefinition,
46
+ handleAnalyzeQuery
47
+ } from './tools/analyze-query.js';
48
+
49
+ export class DatabaseInspectorServer {
50
+ private server: Server;
51
+ private dbManager: DatabaseManager;
52
+
53
+ constructor() {
54
+ this.server = new Server(
55
+ {
56
+ name: 'mcp-database-inspector',
57
+ version: '2.0.1',
58
+ },
59
+ {
60
+ capabilities: {
61
+ tools: {},
62
+ },
63
+ }
64
+ );
65
+
66
+ this.dbManager = new DatabaseManager();
67
+ this.setupHandlers();
68
+ }
69
+
70
+ private setupHandlers(): void {
71
+ // List available tools
72
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
73
+ Logger.debug('Received list_tools request');
74
+
75
+ return {
76
+ tools: [
77
+ listDatabasesToolDefinition,
78
+ listTablesToolDefinition,
79
+ inspectTableToolDefinition,
80
+ getForeignKeysToolDefinition,
81
+ getIndexesToolDefinition,
82
+ informationSchemaQueryToolDefinition,
83
+ executeQueryToolDefinition,
84
+ analyzeQueryToolDefinition
85
+ ]
86
+ };
87
+ });
88
+
89
+ // Handle tool calls
90
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
91
+ const { name, arguments: args } = request.params;
92
+
93
+ Logger.info(`Received tool call: ${name}`);
94
+ Logger.debug(`Tool arguments:`, args);
95
+
96
+ try {
97
+ switch (name) {
98
+ case 'list_databases':
99
+ return await handleListDatabases(args, this.dbManager);
100
+
101
+ case 'list_tables':
102
+ return await handleListTables(args, this.dbManager);
103
+
104
+ case 'inspect_table':
105
+ return await handleInspectTable(args, this.dbManager);
106
+
107
+ case 'get_foreign_keys':
108
+ return await handleGetForeignKeys(args, this.dbManager);
109
+
110
+ case 'get_indexes':
111
+ return await handleGetIndexes(args, this.dbManager);
112
+
113
+ case 'information_schema_query':
114
+ return await handleInformationSchemaQuery(args, this.dbManager);
115
+
116
+ case 'execute_query':
117
+ return await handleExecuteQuery(args, this.dbManager);
118
+
119
+ case 'analyze_query':
120
+ return await handleAnalyzeQuery(args, this.dbManager);
121
+
122
+ default:
123
+ Logger.warn(`Unknown tool requested: ${name}`);
124
+ throw new ToolError(`Unknown tool: ${name}`);
125
+ }
126
+ } catch (error) {
127
+ Logger.error(`Error executing tool ${name}:`, error);
128
+
129
+ const errorResponse = createErrorResponse(error instanceof Error ? error : new Error('Unknown error'));
130
+
131
+ return {
132
+ content: [{
133
+ type: 'text',
134
+ text: `Error: ${errorResponse.error}`
135
+ }],
136
+ isError: true
137
+ };
138
+ }
139
+ });
140
+
141
+ // Handle server errors
142
+ this.server.onerror = (error) => {
143
+ Logger.error('Server error:', error);
144
+ };
145
+ }
146
+
147
+ async initialize(connectionUrls: string[]): Promise<void> {
148
+ Logger.info('Initializing MySQL Inspector Server');
149
+ Logger.info(`Connection URLs provided: ${connectionUrls.length}`);
150
+
151
+ if (connectionUrls.length === 0) {
152
+ throw new Error('At least one database connection URL is required');
153
+ }
154
+
155
+ // Add databases to manager
156
+ for (let i = 0; i < connectionUrls.length; i++) {
157
+ const url = connectionUrls[i];
158
+ try {
159
+ Logger.info(`Adding database connection ${i + 1}/${connectionUrls.length}`);
160
+ const dbName = await this.dbManager.addDatabase(url);
161
+ Logger.info(`Successfully added database: ${dbName}`);
162
+ } catch (error) {
163
+ Logger.error(`Failed to add database from URL: ${url}`, error);
164
+ // Continue with other databases instead of failing completely
165
+ }
166
+ }
167
+
168
+ const databases = this.dbManager.listDatabases();
169
+ if (databases.length === 0) {
170
+ throw new Error('No valid database connections could be established');
171
+ }
172
+
173
+ Logger.info(`Server initialized with ${databases.length} database(s): ${databases.map(db => db.name).join(', ')}`);
174
+ }
175
+
176
+ async run(): Promise<void> {
177
+ Logger.info('Starting MySQL Inspector MCP Server');
178
+
179
+ const transport = new StdioServerTransport();
180
+ await this.server.connect(transport);
181
+
182
+ Logger.info('Server started and listening for requests');
183
+ }
184
+
185
+ async shutdown(): Promise<void> {
186
+ Logger.info('Shutting down MySQL Inspector Server');
187
+
188
+ try {
189
+ await this.dbManager.cleanup();
190
+ Logger.info('Database connections cleaned up');
191
+ } catch (error) {
192
+ Logger.error('Error during database cleanup:', error);
193
+ }
194
+
195
+ Logger.info('Server shutdown complete');
196
+ }
197
+
198
+ // Utility methods for external use
199
+ getDatabaseManager(): DatabaseManager {
200
+ return this.dbManager;
201
+ }
202
+
203
+ async getServerInfo(): Promise<any> {
204
+ const databases = this.dbManager.listDatabases();
205
+ return {
206
+ serverName: 'mcp-database-inspector',
207
+ version: '2.0.0',
208
+ connectedDatabases: databases.length,
209
+ databases: databases.map(db => ({
210
+ name: db.name,
211
+ host: db.host,
212
+ database: db.database,
213
+ connected: db.connected,
214
+ lastUsed: db.lastUsed.toISOString()
215
+ })),
216
+ capabilities: [
217
+ 'list_databases',
218
+ 'list_tables',
219
+ 'inspect_table',
220
+ 'get_foreign_keys',
221
+ 'get_foreign_keys',
222
+ 'get_indexes',
223
+ 'information_schema_query',
224
+ 'execute_query',
225
+ 'analyze_query'
226
+ ],
227
+ status: databases.length > 0 ? 'ready' : 'no_connections'
228
+ };
229
+ }
230
+ }
231
+
232
+ // Error handling for uncaught exceptions
233
+ process.on('uncaughtException', (error) => {
234
+ Logger.error('Uncaught exception:', error);
235
+ process.exit(1);
236
+ });
237
+
238
+ process.on('unhandledRejection', (reason, promise) => {
239
+ Logger.error('Unhandled rejection:', { promise, reason });
240
+ process.exit(1);
241
+ });
242
+
243
+ // Graceful shutdown
244
+ process.on('SIGINT', async () => {
245
+ Logger.info('Received SIGINT, shutting down gracefully...');
246
+ process.exit(0);
247
+ });
248
+
249
+ process.on('SIGTERM', async () => {
250
+ Logger.info('Received SIGTERM, shutting down gracefully...');
251
+ process.exit(0);
252
+ });
253
+
254
+ export default DatabaseInspectorServer;
@@ -0,0 +1,63 @@
1
+ import { DatabaseManager } from './database/manager.js';
2
+ import { Logger } from './utils/logger.js';
3
+
4
+ async function testConnections() {
5
+ const dbManager = new DatabaseManager();
6
+
7
+ // Default credentials
8
+ const mysqlUrl = 'mysql://root:root@localhost:3306/mysql';
9
+ const postgresUrl = 'postgresql://postgres:postgres@localhost:5432/postgres';
10
+
11
+ Logger.setLogLevel('info');
12
+ console.log('Starting connectivity test with default credentials...');
13
+
14
+ // Test MySQL
15
+ console.log('\n--- Testing MySQL (root/root) ---');
16
+ try {
17
+ const mysqlName = await dbManager.addDatabase(mysqlUrl, 'mysql_default');
18
+ console.log(`✅ Successfully added MySQL database: ${mysqlName}`);
19
+
20
+ const tables = await dbManager.getTables(mysqlName);
21
+ console.log(`✅ Retrieved ${tables.length} tables from MySQL`);
22
+ if (tables.length > 0) {
23
+ const table = tables[0].tableName;
24
+ console.log(`\n--- Analyzing MySQL Query on ${table} ---`);
25
+ const analysis = await dbManager.analyzeQuery(mysqlName, `SELECT * FROM ${table} LIMIT 10`);
26
+ console.log('✅ Analysis Success');
27
+ console.log(' Cost:', analysis.summary.cost);
28
+ console.log(' Operations:', analysis.summary.operations.join(', '));
29
+ if (analysis.summary.potentialIssues.length > 0) {
30
+ console.log(' ⚠️ Issues:', analysis.summary.potentialIssues.join(', '));
31
+ }
32
+ }
33
+ } catch (error) {
34
+ console.error(`❌ MySQL test failed: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+
37
+ // Test PostgreSQL
38
+ console.log('\n--- Testing PostgreSQL (postgres/postgres) ---');
39
+ try {
40
+ const pgName = await dbManager.addDatabase(postgresUrl, 'postgres_default');
41
+ console.log(`✅ Successfully added PostgreSQL database: ${pgName}`);
42
+
43
+ console.log(`\n--- Analyzing PostgreSQL Query ---`);
44
+ // Use a system table that always exists
45
+ const analysis = await dbManager.analyzeQuery(pgName, 'SELECT * FROM pg_catalog.pg_class LIMIT 5');
46
+ console.log('✅ Analysis Success');
47
+ console.log(' Cost:', analysis.summary.cost);
48
+ console.log(' Operations:', analysis.summary.operations.join(', '));
49
+ if (analysis.summary.potentialIssues.length > 0) {
50
+ console.log(' ⚠️ Issues:', analysis.summary.potentialIssues.join(', '));
51
+ }
52
+ } catch (error) {
53
+ console.error(`❌ PostgreSQL test failed: ${error instanceof Error ? error.message : String(error)}`);
54
+ }
55
+
56
+ await dbManager.cleanup();
57
+ console.log('\nTest complete.');
58
+ }
59
+
60
+ testConnections().catch(err => {
61
+ console.error('Fatal error during test:', err);
62
+ process.exit(1);
63
+ });