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,100 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { handleAnalyzeQuery } from './analyze-query.js';
3
+ import { DatabaseManager } from '../database/manager.js';
4
+
5
+ describe('analyze_query', () => {
6
+ let mockDbManager: any;
7
+
8
+ beforeEach(() => {
9
+ mockDbManager = {
10
+ analyzeQuery: vi.fn()
11
+ };
12
+ });
13
+
14
+ it('should analyze a MySQL query and return execution plan', async () => {
15
+ const mockAnalysis = {
16
+ database: 'testdb',
17
+ type: 'MySQL',
18
+ query: 'SELECT * FROM users',
19
+ plan: { query_block: { cost_info: { query_cost: 1.5 } } },
20
+ summary: {
21
+ cost: 1.5,
22
+ operations: ['ALL'],
23
+ potentialIssues: ['Full table scan on users']
24
+ }
25
+ };
26
+
27
+ mockDbManager.analyzeQuery.mockResolvedValueOnce(mockAnalysis);
28
+
29
+ const result = await handleAnalyzeQuery(
30
+ { database: 'testdb', query: 'SELECT * FROM users' },
31
+ mockDbManager
32
+ );
33
+
34
+ const parsed = JSON.parse(result.content[0].text);
35
+ expect(parsed.summary.cost).toBe(1.5);
36
+ expect(parsed.summary.potentialIssues).toContain('Full table scan on users');
37
+ expect(parsed.summary.recommendation).toContain('potential performance issues');
38
+ });
39
+
40
+ it('should analyze a PostgreSQL query and return execution plan', async () => {
41
+ const mockAnalysis = {
42
+ database: 'testdb',
43
+ type: 'PostgreSQL',
44
+ query: 'SELECT * FROM products',
45
+ plan: { Plan: { 'Total Cost': 2.5, 'Node Type': 'Seq Scan' } },
46
+ summary: {
47
+ cost: 2.5,
48
+ operations: ['Seq Scan'],
49
+ potentialIssues: ['Full table scan on products']
50
+ }
51
+ };
52
+
53
+ mockDbManager.analyzeQuery.mockResolvedValueOnce(mockAnalysis);
54
+
55
+ const result = await handleAnalyzeQuery(
56
+ { database: 'testdb', query: 'SELECT * FROM products' },
57
+ mockDbManager
58
+ );
59
+
60
+ const parsed = JSON.parse(result.content[0].text);
61
+ expect(parsed.summary.cost).toBe(2.5);
62
+ expect(parsed.summary.operations).toContain('Seq Scan');
63
+ });
64
+
65
+ it('should return positive recommendation when no issues found', async () => {
66
+ const mockAnalysis = {
67
+ database: 'testdb',
68
+ type: 'MySQL',
69
+ query: 'SELECT * FROM users WHERE id = 1',
70
+ plan: { query_block: { cost_info: { query_cost: 0.35 } } },
71
+ summary: {
72
+ cost: 0.35,
73
+ operations: ['const'],
74
+ potentialIssues: []
75
+ }
76
+ };
77
+
78
+ mockDbManager.analyzeQuery.mockResolvedValueOnce(mockAnalysis);
79
+
80
+ const result = await handleAnalyzeQuery(
81
+ { database: 'testdb', query: 'SELECT * FROM users WHERE id = 1' },
82
+ mockDbManager
83
+ );
84
+
85
+ const parsed = JSON.parse(result.content[0].text);
86
+ expect(parsed.summary.recommendation).toContain('No major performance issues');
87
+ });
88
+
89
+ it('should throw error if neither database nor query is provided', async () => {
90
+ await expect(() =>
91
+ handleAnalyzeQuery({ database: 'testdb' }, mockDbManager)
92
+ ).rejects.toThrow(/Invalid arguments/);
93
+ });
94
+
95
+ it('should throw error for invalid database name', async () => {
96
+ await expect(() =>
97
+ handleAnalyzeQuery({ database: '', query: 'SELECT 1' }, mockDbManager)
98
+ ).rejects.toThrow(/Invalid arguments/);
99
+ });
100
+ });
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+ import { DatabaseManager } from '../database/manager.js';
3
+ import { InputValidator } from '../validators/input-validator.js';
4
+ import { Logger } from '../utils/logger.js';
5
+ import { ToolError } from '../utils/errors.js';
6
+
7
+ // Tool schema
8
+ export const AnalyzeQueryArgsSchema = z.object({
9
+ database: z.string().min(1, 'Database name is required'),
10
+ query: z.string().min(1, 'SQL query is required')
11
+ });
12
+
13
+ export interface AnalyzeQueryTool {
14
+ name: 'analyze_query';
15
+ description: 'Analyze a SQL query performance and provide recommendations';
16
+ inputSchema: {
17
+ type: 'object';
18
+ properties: {
19
+ database: {
20
+ type: 'string';
21
+ description: 'Name of the database to run the analysis against';
22
+ };
23
+ query: {
24
+ type: 'string';
25
+ description: 'The SQL query to analyze';
26
+ };
27
+ };
28
+ required: ['database', 'query'];
29
+ };
30
+ }
31
+
32
+ export const analyzeQueryToolDefinition: AnalyzeQueryTool = {
33
+ name: 'analyze_query',
34
+ description: 'Analyze a SQL query performance and provide recommendations',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ database: {
39
+ type: 'string',
40
+ description: 'Name of the database to run the analysis against'
41
+ },
42
+ query: {
43
+ type: 'string',
44
+ description: 'The SQL query to analyze'
45
+ }
46
+ },
47
+ required: ['database', 'query']
48
+ }
49
+ };
50
+
51
+ export async function handleAnalyzeQuery(
52
+ args: unknown,
53
+ dbManager: DatabaseManager
54
+ ): Promise<any> {
55
+ try {
56
+ Logger.info('Executing analyze_query tool');
57
+
58
+ // Validate arguments
59
+ const validationResult = AnalyzeQueryArgsSchema.safeParse(args);
60
+ if (!validationResult.success) {
61
+ Logger.warn('Invalid arguments for analyze_query', validationResult.error);
62
+ throw new ToolError(
63
+ `Invalid arguments: ${validationResult.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
64
+ 'analyze_query'
65
+ );
66
+ }
67
+
68
+ const { database, query } = validationResult.data;
69
+
70
+ // Sanitize database name
71
+ const sanitizedDatabase = InputValidator.sanitizeString(database);
72
+
73
+ Logger.info(`Analyzing query for database: ${sanitizedDatabase}`);
74
+
75
+ const analysis = await dbManager.analyzeQuery(sanitizedDatabase, query);
76
+
77
+ const response = {
78
+ database: sanitizedDatabase,
79
+ query: analysis.query,
80
+ type: analysis.type,
81
+ summary: {
82
+ cost: analysis.summary.cost,
83
+ operations: analysis.summary.operations,
84
+ potentialIssues: analysis.summary.potentialIssues,
85
+ recommendation: analysis.summary.potentialIssues.length > 0
86
+ ? `Found ${analysis.summary.potentialIssues.length} potential performance issues. Consider adding indexes or refactoring the query.`
87
+ : 'No major performance issues detected in the execution plan.'
88
+ },
89
+ executionPlan: analysis.plan
90
+ };
91
+
92
+ return {
93
+ content: [{
94
+ type: 'text',
95
+ text: JSON.stringify(response, null, 2)
96
+ }]
97
+ };
98
+
99
+ } catch (error) {
100
+ Logger.error('Error in analyze_query tool', error);
101
+
102
+ if (error instanceof ToolError) {
103
+ throw error;
104
+ }
105
+
106
+ throw new ToolError(
107
+ `Failed to analyze query: ${error instanceof Error ? error.message : 'Unknown error'}`,
108
+ 'analyze_query',
109
+ error instanceof Error ? error : undefined
110
+ );
111
+ }
112
+ }
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ import { DatabaseManager } from '../database/manager.js';
3
+ import { InputValidator } from '../validators/input-validator.js';
4
+ import { Logger } from '../utils/logger.js';
5
+ import { ToolError } from '../utils/errors.js';
6
+
7
+ // Zod schema for arguments
8
+ const ExecuteQueryArgsSchema = z.object({
9
+ database: z.string().min(1, 'Database name is required'),
10
+ query: z.string().min(1, 'SQL query is required'),
11
+ params: z.array(z.any()).optional(),
12
+ limit: z.number().int().min(1).max(1000).optional()
13
+ });
14
+
15
+ export interface ExecuteQueryTool {
16
+ name: 'execute_query';
17
+ description: 'Execute a safe read-only SQL query (SELECT, SHOW, DESCRIBE, EXPLAIN). Automatic row limits are applied for security.';
18
+ inputSchema: {
19
+ type: 'object';
20
+ properties: {
21
+ database: { type: 'string'; description: 'Database name' };
22
+ query: { type: 'string'; description: 'SQL query to execute' };
23
+ params?: { type: 'array'; items: { type: 'any' }; description: 'Optional parameters for the query' };
24
+ limit?: { type: 'number'; description: 'Optional limit for number of rows (default 1000, max 1000)' };
25
+ };
26
+ required: ['database', 'query'];
27
+ };
28
+ }
29
+
30
+ export const executeQueryToolDefinition: ExecuteQueryTool = {
31
+ name: 'execute_query',
32
+ description: 'Execute a safe read-only SQL query (SELECT, SHOW, DESCRIBE, EXPLAIN). Automatic row limits are applied for security.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ database: { type: 'string', description: 'Database name' },
37
+ query: { type: 'string', description: 'SQL query to execute' },
38
+ params: { type: 'array', items: { type: 'any' }, description: 'Optional parameters for the query' },
39
+ limit: { type: 'number', description: 'Optional limit for number of rows (default 1000, max 1000)' }
40
+ },
41
+ required: ['database', 'query']
42
+ }
43
+ };
44
+
45
+ export async function handleExecuteQuery(
46
+ args: unknown,
47
+ dbManager: DatabaseManager
48
+ ): Promise<any> {
49
+ try {
50
+ Logger.info('Executing execute_query tool');
51
+ const validationResult = ExecuteQueryArgsSchema.safeParse(args);
52
+ if (!validationResult.success) {
53
+ Logger.warn('Invalid arguments for execute_query', validationResult.error);
54
+ throw new ToolError(
55
+ `Invalid arguments: ${validationResult.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
56
+ 'execute_query'
57
+ );
58
+ }
59
+
60
+ const { database, query, params, limit } = validationResult.data;
61
+
62
+ // Sanitize and validate database name
63
+ const sanitizedDatabase = InputValidator.sanitizeString(database);
64
+ const dbNameValidation = InputValidator.validateDatabaseName(sanitizedDatabase);
65
+ if (!dbNameValidation.isValid) {
66
+ throw new ToolError(
67
+ `Invalid database name: ${dbNameValidation.error}`,
68
+ 'execute_query'
69
+ );
70
+ }
71
+
72
+ Logger.info(`Executing query on database ${sanitizedDatabase}`);
73
+
74
+ const result = await dbManager.executeQuery(sanitizedDatabase, query, params);
75
+
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: JSON.stringify(result.rows, null, 2)
80
+ }]
81
+ };
82
+ } catch (error) {
83
+ Logger.error('Error in execute_query tool', error);
84
+ if (error instanceof ToolError) throw error;
85
+ throw new ToolError(
86
+ `Failed to execute query: ${error instanceof Error ? error.message : 'Unknown error'}`,
87
+ 'execute_query',
88
+ error instanceof Error ? error : undefined
89
+ );
90
+ }
91
+ }
@@ -0,0 +1,51 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { handleGetForeignKeys } from './get-foreign-keys';
4
+ import { DatabaseManager } from '../database/manager';
5
+
6
+ const mockDbManager = {
7
+ getForeignKeys: vi.fn()
8
+ } as unknown as DatabaseManager;
9
+
10
+ describe('get_foreign_keys', () => {
11
+ it('should return foreign keys for a single table', async () => {
12
+ mockDbManager.getForeignKeys.mockResolvedValueOnce([{ constraintName: 'fk_orders_users', tableName: 'orders', columnName: 'user_id', referencedTableName: 'users', referencedColumnName: 'id' }]);
13
+ const args = { database: 'testdb', table: 'orders' };
14
+ const result = await handleGetForeignKeys(args, mockDbManager);
15
+ const parsed = JSON.parse(result.content[0].text);
16
+ expect(parsed.table).toBe('orders');
17
+ expect(parsed.foreignKeys[0].sourceColumn).toBe('user_id');
18
+ });
19
+
20
+ it('should return foreign keys for multiple tables', async () => {
21
+ mockDbManager.getForeignKeys
22
+ .mockResolvedValueOnce([{ constraintName: 'fk_orders_users', tableName: 'orders', columnName: 'user_id', referencedTableName: 'users', referencedColumnName: 'id' }])
23
+ .mockResolvedValueOnce([{ constraintName: 'fk_items_orders', tableName: 'order_items', columnName: 'order_id', referencedTableName: 'orders', referencedColumnName: 'id' }]);
24
+ const args = { database: 'testdb', tables: ['orders', 'order_items'] };
25
+ const result = await handleGetForeignKeys(args, mockDbManager);
26
+ const parsed = JSON.parse(result.content[0].text);
27
+ expect(Object.keys(parsed.results)).toHaveLength(2);
28
+ expect(parsed.results.orders.table).toBe('orders');
29
+ expect(parsed.results.order_items.table).toBe('order_items');
30
+ });
31
+
32
+ it('should throw error if neither table nor tables is provided', async () => {
33
+ await expect(() =>
34
+ handleGetForeignKeys({ database: 'testdb' }, mockDbManager)
35
+ ).rejects.toThrow(/Either 'table' or non-empty 'tables'/);
36
+ });
37
+
38
+ it('should throw error for invalid table name', async () => {
39
+ await expect(() =>
40
+ handleGetForeignKeys({ database: 'testdb', table: 'invalid;DROP' }, mockDbManager)
41
+ ).rejects.toThrow(/Invalid table name/);
42
+ });
43
+
44
+ it('should return empty result if table not found', async () => {
45
+ mockDbManager.getForeignKeys.mockResolvedValueOnce([]);
46
+ const result = await handleGetForeignKeys({ database: 'testdb', table: 'notfound' }, mockDbManager);
47
+ const parsed = JSON.parse(result.content[0].text);
48
+ expect(parsed.foreignKeys).toHaveLength(0);
49
+ expect(parsed.summary.message).toMatch(/No foreign key relationships found/);
50
+ });
51
+ });