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.
- package/README.md +197 -0
- package/dist/database/connection.d.ts +13 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +155 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/manager.d.ts +28 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +621 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/postgres-connection.d.ts +10 -0
- package/dist/database/postgres-connection.d.ts.map +1 -0
- package/dist/database/postgres-connection.js +113 -0
- package/dist/database/postgres-connection.js.map +1 -0
- package/dist/database/types.d.ts +84 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +6 -0
- package/dist/database/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +186 -0
- package/dist/server.js.map +1 -0
- package/dist/test-defaults.d.ts +2 -0
- package/dist/test-defaults.d.ts.map +1 -0
- package/dist/test-defaults.js +57 -0
- package/dist/test-defaults.js.map +1 -0
- package/dist/tools/analyze-query.d.ts +27 -0
- package/dist/tools/analyze-query.d.ts.map +1 -0
- package/dist/tools/analyze-query.js +71 -0
- package/dist/tools/analyze-query.js.map +1 -0
- package/dist/tools/execute-query.d.ts +33 -0
- package/dist/tools/execute-query.d.ts.map +1 -0
- package/dist/tools/execute-query.js +57 -0
- package/dist/tools/execute-query.js.map +1 -0
- package/dist/tools/get-foreign-keys.d.ts +38 -0
- package/dist/tools/get-foreign-keys.d.ts.map +1 -0
- package/dist/tools/get-foreign-keys.js +391 -0
- package/dist/tools/get-foreign-keys.js.map +1 -0
- package/dist/tools/get-indexes.d.ts +38 -0
- package/dist/tools/get-indexes.d.ts.map +1 -0
- package/dist/tools/get-indexes.js +472 -0
- package/dist/tools/get-indexes.js.map +1 -0
- package/dist/tools/information-schema-query.d.ts +33 -0
- package/dist/tools/information-schema-query.d.ts.map +1 -0
- package/dist/tools/information-schema-query.js +76 -0
- package/dist/tools/information-schema-query.js.map +1 -0
- package/dist/tools/inspect-table.d.ts +38 -0
- package/dist/tools/inspect-table.d.ts.map +1 -0
- package/dist/tools/inspect-table.js +351 -0
- package/dist/tools/inspect-table.js.map +1 -0
- package/dist/tools/list-databases.d.ts +14 -0
- package/dist/tools/list-databases.d.ts.map +1 -0
- package/dist/tools/list-databases.js +83 -0
- package/dist/tools/list-databases.js.map +1 -0
- package/dist/tools/list-tables.d.ts +19 -0
- package/dist/tools/list-tables.d.ts.map +1 -0
- package/dist/tools/list-tables.js +130 -0
- package/dist/tools/list-tables.js.map +1 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +98 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +28 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +132 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/validators/input-validator.d.ts +76 -0
- package/dist/validators/input-validator.d.ts.map +1 -0
- package/dist/validators/input-validator.js +295 -0
- package/dist/validators/input-validator.js.map +1 -0
- package/dist/validators/query-validator.d.ts +19 -0
- package/dist/validators/query-validator.d.ts.map +1 -0
- package/dist/validators/query-validator.js +229 -0
- package/dist/validators/query-validator.js.map +1 -0
- package/enhanced_sql_prompt.md +324 -0
- package/examples/claude-config.json +23 -0
- package/examples/roo-config.json +16 -0
- package/package.json +42 -0
- package/src/database/connection.ts +165 -0
- package/src/database/manager.ts +682 -0
- package/src/database/postgres-connection.ts +123 -0
- package/src/database/types.ts +93 -0
- package/src/index.ts +136 -0
- package/src/server.ts +254 -0
- package/src/test-defaults.ts +63 -0
- package/src/tools/analyze-query.test.ts +100 -0
- package/src/tools/analyze-query.ts +112 -0
- package/src/tools/execute-query.ts +91 -0
- package/src/tools/get-foreign-keys.test.ts +51 -0
- package/src/tools/get-foreign-keys.ts +488 -0
- package/src/tools/get-indexes.test.ts +51 -0
- package/src/tools/get-indexes.ts +570 -0
- package/src/tools/information-schema-query.ts +125 -0
- package/src/tools/inspect-table.test.ts +59 -0
- package/src/tools/inspect-table.ts +440 -0
- package/src/tools/list-databases.ts +119 -0
- package/src/tools/list-tables.ts +181 -0
- package/src/utils/errors.ts +103 -0
- package/src/utils/logger.ts +158 -0
- package/src/validators/input-validator.ts +318 -0
- package/src/validators/query-validator.ts +267 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Enhanced SQL Expert Prompt with Natural Language Analysis
|
|
2
|
+
|
|
3
|
+
## Role Definition
|
|
4
|
+
As an SQL expert with access to MCP Database Inspector tools, you must analyze natural language queries systematically and execute precise database operations.
|
|
5
|
+
|
|
6
|
+
## Step-by-Step Natural Language Analysis Framework
|
|
7
|
+
|
|
8
|
+
### Step 1: Query Intent Classification
|
|
9
|
+
Analyze the user's question to determine the primary intent:
|
|
10
|
+
- **Schema Discovery**: "What tables exist?", "List all databases"
|
|
11
|
+
- **Table Structure**: "Show me columns in X", "What's the schema of Y?"
|
|
12
|
+
- **Relationship Analysis**: "What are the foreign keys?", "How are tables connected?", "How might these tables relate?"
|
|
13
|
+
- **Data Constraints**: "What are primary keys?", "Show me indexes"
|
|
14
|
+
- **Quantitative Analysis**: "How many tables have column X?", "Count tables with Y"
|
|
15
|
+
- **Join Recommendations**: "How should I join these tables?", "What's the best way to connect table X and Y?"
|
|
16
|
+
|
|
17
|
+
### Step 2: Entity Extraction
|
|
18
|
+
Identify key entities from the natural language:
|
|
19
|
+
- **Database Name**: Extract explicit database references or use context
|
|
20
|
+
- **Table Name(s)**: Identify specific tables or determine if all tables needed
|
|
21
|
+
- **Column Name(s)**: Extract specific column references
|
|
22
|
+
- **Constraint Types**: Primary keys, foreign keys, indexes, etc.
|
|
23
|
+
- **Quantifiers**: "all", "count", "how many", specific numbers
|
|
24
|
+
|
|
25
|
+
**CRITICAL**: After entity extraction, MUST validate all table and column names against actual schema before proceeding.
|
|
26
|
+
|
|
27
|
+
### Step 3: Tool Selection Matrix
|
|
28
|
+
Based on intent and entities, select the appropriate MCP tool:
|
|
29
|
+
|
|
30
|
+
| Intent | Required Info | Tool to Use |
|
|
31
|
+
|--------|---------------|-------------|
|
|
32
|
+
| List databases | None | `list_databases` |
|
|
33
|
+
| List tables | Database name | `list_tables` |
|
|
34
|
+
| Table schema | Database + Table | `inspect_table` |
|
|
35
|
+
| Foreign keys | Database (+ optional Table) | `get_foreign_keys` |
|
|
36
|
+
| Index info | Database + Table | `get_indexes` |
|
|
37
|
+
| Cross-table analysis | Multiple tools in sequence | Chain multiple tools |
|
|
38
|
+
| Implicit relationship analysis | Database + Tables involved | `list_tables` → `inspect_table` (for each table) → analyze naming patterns and types |
|
|
39
|
+
| Join recommendation | Database + Tables involved | `inspect_table` (for each table) → analyze for joinable columns
|
|
40
|
+
|
|
41
|
+
### Step 4: Parameter Mapping
|
|
42
|
+
Map extracted entities to tool parameters:
|
|
43
|
+
- Ensure required parameters are present
|
|
44
|
+
- Handle optional parameters appropriately
|
|
45
|
+
- Validate parameter format and structure
|
|
46
|
+
- **MANDATORY**: Verify all table names exist using `list_tables`
|
|
47
|
+
- **MANDATORY**: Verify all column names exist using `inspect_table`
|
|
48
|
+
|
|
49
|
+
### Step 5: Query Execution Strategy
|
|
50
|
+
For complex queries requiring multiple steps:
|
|
51
|
+
1. **VALIDATION PHASE**: Verify all referenced tables and columns exist
|
|
52
|
+
2. Start with broad discovery (databases → tables)
|
|
53
|
+
3. Narrow to specific inspection (table schemas, relationships)
|
|
54
|
+
4. Aggregate results if quantitative analysis needed
|
|
55
|
+
|
|
56
|
+
**Schema Validation Rules**:
|
|
57
|
+
- NEVER assume table or column names
|
|
58
|
+
- ALWAYS use exact names from schema inspection
|
|
59
|
+
- If user references non-existent entities, provide corrections
|
|
60
|
+
- Use case-sensitive matching for names
|
|
61
|
+
|
|
62
|
+
## Enhanced Execution Rules
|
|
63
|
+
|
|
64
|
+
### Pre-Execution Checklist:
|
|
65
|
+
- [ ] Intent clearly identified
|
|
66
|
+
- [ ] All required entities extracted
|
|
67
|
+
- [ ] Appropriate tool selected
|
|
68
|
+
- [ ] Parameters properly formatted
|
|
69
|
+
- [ ] Execution order planned (for multi-step queries)
|
|
70
|
+
- [ ] Implicit relationship strategy determined (for databases without foreign keys)
|
|
71
|
+
- [ ] **CRITICAL**: All table names validated against actual schema
|
|
72
|
+
- [ ] **CRITICAL**: All column names validated against actual table structure
|
|
73
|
+
- [ ] **CRITICAL**: No assumed or guessed names used in final output
|
|
74
|
+
|
|
75
|
+
### Response Format:
|
|
76
|
+
1. **For Simple Queries**: Present results directly and concisely
|
|
77
|
+
2. **For Complex Queries**: Show intermediate steps, then final aggregated result
|
|
78
|
+
3. **For Error Cases**: Explain missing information and request clarification
|
|
79
|
+
4. **For Name Validation Errors**: Provide exact available names and suggest corrections
|
|
80
|
+
|
|
81
|
+
### Name Validation Protocol:
|
|
82
|
+
**BEFORE generating any SQL or making claims about schema:**
|
|
83
|
+
1. Validate database exists using `list_databases`
|
|
84
|
+
2. Validate table exists using `list_tables` for the specified database
|
|
85
|
+
3. Validate columns exist using `inspect_table` for each referenced table
|
|
86
|
+
4. Use EXACT names from schema results in all outputs
|
|
87
|
+
5. Never use placeholder names, assumed names, or variations
|
|
88
|
+
|
|
89
|
+
### Example Analysis Patterns:
|
|
90
|
+
|
|
91
|
+
#### Pattern 1: Quantitative Cross-Table Query
|
|
92
|
+
**User Query**: "How many tables in steam_price contain a column named 'price'?"
|
|
93
|
+
|
|
94
|
+
**Analysis**:
|
|
95
|
+
- Intent: Quantitative Analysis
|
|
96
|
+
- Database: steam_price
|
|
97
|
+
- Column: price
|
|
98
|
+
- Required Tools: `list_tables` → `inspect_table` (for each table) → count matching
|
|
99
|
+
|
|
100
|
+
**Execution with Validation**:
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"validation": "list_databases() → verify 'steam_price' exists",
|
|
104
|
+
"step1": "list_tables(database='steam_price')",
|
|
105
|
+
"step2": "inspect_table(database='steam_price', table=<each_actual_table_name>)",
|
|
106
|
+
"step3": "filter_and_count_tables_with_exact_column_name('price')"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Critical**: Use exact table names from step1 results, exact column names from step2 results.
|
|
111
|
+
|
|
112
|
+
#### Pattern 2: Relationship Discovery
|
|
113
|
+
**User Query**: "Show me all foreign key relationships in steamtrade database"
|
|
114
|
+
|
|
115
|
+
**Analysis**:
|
|
116
|
+
- Intent: Relationship Analysis
|
|
117
|
+
- Database: steamtrade
|
|
118
|
+
- Required Tools: `get_foreign_keys`
|
|
119
|
+
|
|
120
|
+
**Execution with Validation**:
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"validation": "list_databases() → verify 'steamtrade' exists",
|
|
124
|
+
"step1": "get_foreign_keys(database='steamtrade')"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Critical**: Database name must match exactly from validation step.
|
|
129
|
+
|
|
130
|
+
#### Pattern 3: Schema Comparison
|
|
131
|
+
**User Query**: "What columns do the user and profile tables have in common?"
|
|
132
|
+
|
|
133
|
+
**Analysis**:
|
|
134
|
+
- Intent: Table Structure Comparison
|
|
135
|
+
- Tables: user, profile (MUST VALIDATE THESE EXIST)
|
|
136
|
+
- Required Tools: `inspect_table` (for both) → compare schemas
|
|
137
|
+
|
|
138
|
+
**Execution with Validation**:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"validation": "list_tables(database=<context>) → verify 'user' and 'profile' exist",
|
|
142
|
+
"step1": "inspect_table(database=<context>, table='<exact_user_table_name>')",
|
|
143
|
+
"step2": "inspect_table(database=<context>, table='<exact_profile_table_name>')",
|
|
144
|
+
"step3": "find_common_columns_using_exact_names()"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Critical**: Replace `<exact_user_table_name>` and `<exact_profile_table_name>` with actual table names from validation step.
|
|
149
|
+
|
|
150
|
+
#### Pattern 4: Implicit Relationship Discovery
|
|
151
|
+
**User Query**: "How might the orders and customers tables be related?"
|
|
152
|
+
|
|
153
|
+
**Analysis**:
|
|
154
|
+
- Intent: Implicit Relationship Analysis
|
|
155
|
+
- Tables: orders, customers (MUST VALIDATE THESE EXIST)
|
|
156
|
+
- Required Tools: `inspect_table` (for both) → analyze naming patterns and data types
|
|
157
|
+
|
|
158
|
+
**Execution with Validation**:
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"validation": "list_tables(database=<context>) → find actual table names matching 'orders' and 'customers'",
|
|
162
|
+
"step1": "inspect_table(database=<context>, table='<exact_orders_table_name>')",
|
|
163
|
+
"step2": "inspect_table(database=<context>, table='<exact_customers_table_name>')",
|
|
164
|
+
"step3": "analyze_potential_join_columns_using_exact_names()"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Name Matching Strategy**: If user says "orders" but actual table is "order_history", use the actual name and inform the user.
|
|
169
|
+
|
|
170
|
+
**Implicit Join Column Detection Logic**:
|
|
171
|
+
1. Identify primary keys in both tables
|
|
172
|
+
2. Look for columns with matching names (exact or pattern matches):
|
|
173
|
+
- Exact: `customer_id` in both tables
|
|
174
|
+
- Pattern: `id` in customers table matches `customer_id` in orders table
|
|
175
|
+
3. Check for compatible data types
|
|
176
|
+
4. Check for naming conventions:
|
|
177
|
+
- table_name + '_id' in related table
|
|
178
|
+
- Singular/plural variants (customer_id in orders table → customers table)
|
|
179
|
+
5. Compare column value patterns and statistical properties if possible
|
|
180
|
+
|
|
181
|
+
#### Pattern 5: Join Recommendation
|
|
182
|
+
**User Query**: "What's the best way to join the products and categories tables?"
|
|
183
|
+
|
|
184
|
+
**Analysis**:
|
|
185
|
+
- Intent: Join Recommendation
|
|
186
|
+
- Tables: products, categories (MUST VALIDATE THESE EXIST)
|
|
187
|
+
- Required Tools: `inspect_table` (for both) → identify joinable columns
|
|
188
|
+
|
|
189
|
+
**Execution with Validation**:
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"validation": "list_tables(database=<context>) → find actual table names matching 'products' and 'categories'",
|
|
193
|
+
"step1": "inspect_table(database=<context>, table='<exact_products_table_name>')",
|
|
194
|
+
"step2": "inspect_table(database=<context>, table='<exact_categories_table_name>')",
|
|
195
|
+
"step3": "recommend_join_columns_using_exact_names()"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Join Recommendation Output Format** (using EXACT names from schema):
|
|
200
|
+
```sql
|
|
201
|
+
-- Example with validated names:
|
|
202
|
+
-- Recommended join (High confidence: naming pattern + type match):
|
|
203
|
+
SELECT * FROM product_catalog p
|
|
204
|
+
JOIN product_categories c ON p.category_id = c.category_id
|
|
205
|
+
|
|
206
|
+
-- Alternative join options (Medium confidence: type match only):
|
|
207
|
+
SELECT * FROM product_catalog p
|
|
208
|
+
JOIN product_categories c ON p.other_actual_column = c.other_actual_column
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**CRITICAL**: Never use assumed names like 'products.category_id' - always use exact column names from `inspect_table` results.
|
|
212
|
+
|
|
213
|
+
## Error Handling and Clarification
|
|
214
|
+
|
|
215
|
+
### When to Ask for Clarification:
|
|
216
|
+
- Database name not specified and cannot be inferred
|
|
217
|
+
- Ambiguous table references
|
|
218
|
+
- Complex queries requiring business logic interpretation
|
|
219
|
+
- Multiple potential join paths with equal likelihood
|
|
220
|
+
- Domain-specific relationship patterns that can't be inferred from schema alone
|
|
221
|
+
- **Referenced table or column names don't exist in actual schema**
|
|
222
|
+
|
|
223
|
+
### Name Validation Errors:
|
|
224
|
+
When user references non-existent names:
|
|
225
|
+
1. **Inform**: "Table/column 'X' doesn't exist"
|
|
226
|
+
2. **Suggest**: "Available tables: [list actual names]"
|
|
227
|
+
3. **Ask**: "Did you mean one of these: [closest matches]?"
|
|
228
|
+
4. **Never proceed** with incorrect names
|
|
229
|
+
|
|
230
|
+
### Standard Clarification Format:
|
|
231
|
+
"To execute this query, I need: [specific missing information]. Please specify [what's needed]."
|
|
232
|
+
|
|
233
|
+
### Name Correction Format:
|
|
234
|
+
"The table/column '[user_input]' doesn't exist in database '[database_name]'. Available options:
|
|
235
|
+
- [exact_name_1]
|
|
236
|
+
- [exact_name_2]
|
|
237
|
+
- [exact_name_3]
|
|
238
|
+
|
|
239
|
+
Did you mean one of these? Please specify the exact name to use."
|
|
240
|
+
|
|
241
|
+
## Output Standards
|
|
242
|
+
|
|
243
|
+
### Successful Query Results:
|
|
244
|
+
- Present data in clean, readable format
|
|
245
|
+
- Use tables/lists for structured data
|
|
246
|
+
- Highlight key findings for complex analyses
|
|
247
|
+
- No conversational filler unless specifically helpful
|
|
248
|
+
- For join recommendations:
|
|
249
|
+
- Provide SQL syntax with confidence level
|
|
250
|
+
- List alternate join options if relevant
|
|
251
|
+
- Explain the reasoning for each join recommendation
|
|
252
|
+
- Include cautions about potential data issues (e.g., many-to-many relationships)
|
|
253
|
+
- Note if indexes exist on the join columns
|
|
254
|
+
|
|
255
|
+
### Failed Query Handling:
|
|
256
|
+
- State the specific issue clearly
|
|
257
|
+
- Suggest alternative approaches if applicable
|
|
258
|
+
- Request only necessary additional information
|
|
259
|
+
- **For name errors**: Always provide the complete list of valid names
|
|
260
|
+
- **Never guess**: If uncertain about names, validate first
|
|
261
|
+
|
|
262
|
+
This framework ensures systematic analysis of natural language database queries and precise execution using the available MCP tools.
|
|
263
|
+
|
|
264
|
+
## Implicit Relationship Detection Algorithm
|
|
265
|
+
|
|
266
|
+
When explicit foreign keys are not available, use this algorithm to identify potential relationships:
|
|
267
|
+
|
|
268
|
+
### Naming Pattern Analysis
|
|
269
|
+
1. **Primary Key to Foreign Key**:
|
|
270
|
+
- Look for `id` in one table matching `[table_name]_id` in another
|
|
271
|
+
- Example: `users.id` → `orders.user_id`
|
|
272
|
+
|
|
273
|
+
2. **Naming Conventions**:
|
|
274
|
+
- Singular/plural variations: `category_id` → `categories`
|
|
275
|
+
- Abbreviations: `prod_id` → `products`
|
|
276
|
+
- Domain-specific patterns: `isbn` in `books` table → `isbn` in `inventory`
|
|
277
|
+
|
|
278
|
+
3. **Column Type Compatibility**:
|
|
279
|
+
- Check for exact type matches (INT-INT, VARCHAR-VARCHAR)
|
|
280
|
+
- Check for compatible types (INT-BIGINT, CHAR-VARCHAR)
|
|
281
|
+
- Rule out incompatible types (INT-DATE, VARCHAR-BOOLEAN)
|
|
282
|
+
|
|
283
|
+
4. **Common Join Patterns by Domain**:
|
|
284
|
+
- E-commerce: orders → products, customers → orders
|
|
285
|
+
- Financial: accounts → transactions, customers → accounts
|
|
286
|
+
- Content: authors → articles, categories → posts
|
|
287
|
+
|
|
288
|
+
### Confidence Scoring
|
|
289
|
+
Assign confidence scores to potential joins:
|
|
290
|
+
|
|
291
|
+
| Criteria | Points |
|
|
292
|
+
|----------|--------|
|
|
293
|
+
| Exact name match (table_id in other table) | 10 points |
|
|
294
|
+
| Pattern match (id to table_id) | 8 points |
|
|
295
|
+
| Type match | 5 points |
|
|
296
|
+
| Column has index | 3 points |
|
|
297
|
+
| Primary key to non-PK match | 2 points |
|
|
298
|
+
| Names semantically related | 1 point |
|
|
299
|
+
|
|
300
|
+
**Confidence Levels**:
|
|
301
|
+
- High: 15+ points
|
|
302
|
+
- Medium: 10-14 points
|
|
303
|
+
- Low: 5-9 points
|
|
304
|
+
- Speculative: <5 points
|
|
305
|
+
|
|
306
|
+
### Output Example
|
|
307
|
+
For implicit relationship recommendations:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
Potential Join Relationships:
|
|
311
|
+
|
|
312
|
+
1. HIGH CONFIDENCE (18 points)
|
|
313
|
+
users.id = orders.user_id
|
|
314
|
+
- Pattern match (8pts): 'id' to 'user_id'
|
|
315
|
+
- Type match (5pts): Both INT
|
|
316
|
+
- Index exists (3pts): orders.user_id is indexed
|
|
317
|
+
- PK to non-PK (2pts): users.id is PK
|
|
318
|
+
|
|
319
|
+
2. MEDIUM CONFIDENCE (12 points)
|
|
320
|
+
products.category_id = categories.id
|
|
321
|
+
- Pattern match (8pts): 'category_id' to 'id'
|
|
322
|
+
- Type match (5pts): Both INT
|
|
323
|
+
- No index on join column (-1pt)
|
|
324
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"mysql-inspector": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": [
|
|
6
|
+
"/path/to/mcp-mysql-inspector/dist/index.js",
|
|
7
|
+
"mysql://username:password@localhost:3306/database1",
|
|
8
|
+
"mysql://username:password@localhost:3306/database2"
|
|
9
|
+
],
|
|
10
|
+
"env": {
|
|
11
|
+
"LOG_LEVEL": "info"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"_comment": "Example Claude Desktop configuration for MCP MySQL Inspector",
|
|
16
|
+
"_instructions": [
|
|
17
|
+
"1. Replace '/path/to/mcp-mysql-inspector/dist/index.js' with the actual path to your installation",
|
|
18
|
+
"2. Replace the database connection URLs with your actual database credentials",
|
|
19
|
+
"3. For global installation, use 'mcp-mysql-inspector' as the command instead of 'node' + path",
|
|
20
|
+
"4. Ensure your database user has SELECT permissions on the target databases",
|
|
21
|
+
"5. Add SSL options to URLs if required: mysql://user:pass@host:port/db?ssl=true"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"servers": {
|
|
3
|
+
"mysql-inspector": {
|
|
4
|
+
"command": "mcp-mysql-inspector",
|
|
5
|
+
"args": [
|
|
6
|
+
"mysql://dev:password@localhost:3306/ecommerce",
|
|
7
|
+
"mysql://dev:password@localhost:3306/analytics",
|
|
8
|
+
"mysql://dev:password@localhost:3306/inventory"
|
|
9
|
+
],
|
|
10
|
+
"env": {
|
|
11
|
+
"LOG_LEVEL": "info"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"description": "Example Roo configuration for MCP MySQL Inspector with multiple databases"
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-database-inspector",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "MCP server for inspecting MySQL and PostgreSQL database schemas and structure",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"clean": "rm -rf dist",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"mysql",
|
|
17
|
+
"database",
|
|
18
|
+
"inspector",
|
|
19
|
+
"schema",
|
|
20
|
+
"ai"
|
|
21
|
+
],
|
|
22
|
+
"author": "kokorolx",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"homepage": "https://github.com/kokorolx/mcp-mysql-inspector",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
27
|
+
"mysql2": "^3.15.3",
|
|
28
|
+
"pg": "^8.13.1",
|
|
29
|
+
"zod": "^4.1.12"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^24.10.1",
|
|
33
|
+
"@types/pg": "^8.11.10",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"mcp-database-inspector": "dist/index.js"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
import { DatabaseType, DatabaseConnectionOptions, QueryResult } from './types.js';
|
|
4
|
+
import { Logger } from '../utils/logger.js';
|
|
5
|
+
import { DatabaseError } from '../utils/errors.js';
|
|
6
|
+
import { PostgresConnection } from './postgres-connection.js';
|
|
7
|
+
|
|
8
|
+
export class DatabaseConnection {
|
|
9
|
+
private static readonly DEFAULT_TIMEOUT = 30000;
|
|
10
|
+
private static readonly DEFAULT_ACQUIRE_TIMEOUT = 60000;
|
|
11
|
+
|
|
12
|
+
static detectDatabaseType(url: string): DatabaseType {
|
|
13
|
+
try {
|
|
14
|
+
const protocol = new URL(url).protocol.replace(':', '');
|
|
15
|
+
if (protocol === 'mysql') return DatabaseType.MySQL;
|
|
16
|
+
if (protocol === 'postgresql' || protocol === 'postgres') return DatabaseType.PostgreSQL;
|
|
17
|
+
throw new DatabaseError(`Unsupported database protocol: ${protocol}. Supported protocols: mysql, postgresql, postgres.`);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error instanceof DatabaseError) throw error;
|
|
20
|
+
throw new DatabaseError(`Invalid connection URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static parseConnectionUrl(url: string): DatabaseConnectionOptions {
|
|
25
|
+
const type = this.detectDatabaseType(url);
|
|
26
|
+
if (type === DatabaseType.PostgreSQL) {
|
|
27
|
+
return PostgresConnection.parseConnectionUrl(url);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MySQL parsing logic (orig)
|
|
31
|
+
try {
|
|
32
|
+
const parsedUrl = new URL(url);
|
|
33
|
+
|
|
34
|
+
const options: DatabaseConnectionOptions = {
|
|
35
|
+
type: DatabaseType.MySQL,
|
|
36
|
+
host: parsedUrl.hostname,
|
|
37
|
+
port: parsedUrl.port ? parseInt(parsedUrl.port) : 3306,
|
|
38
|
+
user: parsedUrl.username,
|
|
39
|
+
password: parsedUrl.password,
|
|
40
|
+
database: parsedUrl.pathname.slice(1), // Remove leading slash
|
|
41
|
+
reconnect: true
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Parse SSL settings from query parameters
|
|
45
|
+
const searchParams = parsedUrl.searchParams;
|
|
46
|
+
if (searchParams.has('ssl')) {
|
|
47
|
+
const sslValue = searchParams.get('ssl');
|
|
48
|
+
if (sslValue === 'true' || sslValue === '1') {
|
|
49
|
+
options.ssl = true;
|
|
50
|
+
} else if (sslValue === 'false' || sslValue === '0') {
|
|
51
|
+
options.ssl = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return options;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new DatabaseError(`Invalid connection URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static async createConnection(options: DatabaseConnectionOptions): Promise<any> {
|
|
62
|
+
if (options.type === DatabaseType.PostgreSQL) {
|
|
63
|
+
return PostgresConnection.createClient(options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
Logger.info(`Connecting to MySQL database at ${options.host}:${options.port}/${options.database}`);
|
|
68
|
+
|
|
69
|
+
const connectionConfig: any = {
|
|
70
|
+
host: options.host,
|
|
71
|
+
port: options.port,
|
|
72
|
+
user: options.user,
|
|
73
|
+
password: options.password,
|
|
74
|
+
database: options.database,
|
|
75
|
+
multipleStatements: false,
|
|
76
|
+
dateStrings: true,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (options.ssl === false) {
|
|
80
|
+
connectionConfig.ssl = false;
|
|
81
|
+
} else if (options.ssl === true) {
|
|
82
|
+
connectionConfig.ssl = {};
|
|
83
|
+
} else if (options.ssl && typeof options.ssl === 'object') {
|
|
84
|
+
connectionConfig.ssl = options.ssl;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const connection = await mysql.createConnection(connectionConfig);
|
|
88
|
+
await connection.ping();
|
|
89
|
+
|
|
90
|
+
Logger.info(`Successfully connected to MySQL database: ${options.database}`);
|
|
91
|
+
return connection;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
Logger.error(`Failed to connect to MySQL database: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
94
|
+
throw new DatabaseError(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static async executeQuery(connection: any, query: string, params?: any[], type: DatabaseType = DatabaseType.MySQL): Promise<QueryResult> {
|
|
99
|
+
if (type === DatabaseType.PostgreSQL) {
|
|
100
|
+
return PostgresConnection.executeQuery(connection, query, params);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
Logger.debug(`Executing MySQL query: ${query.substring(0, 100)}${query.length > 100 ? '...' : ''}`);
|
|
105
|
+
|
|
106
|
+
const [rows, fields] = await connection.execute(query, params);
|
|
107
|
+
|
|
108
|
+
const result: QueryResult = {
|
|
109
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
110
|
+
fields: Array.isArray(fields) ? fields : [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
Logger.debug(`Query executed successfully, returned ${result.rows.length} rows`);
|
|
114
|
+
return result;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
Logger.error(
|
|
117
|
+
`MySQL query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}\n` +
|
|
118
|
+
`Full SQL: ${query}\n` +
|
|
119
|
+
`Params: ${JSON.stringify(params)}`
|
|
120
|
+
);
|
|
121
|
+
throw new DatabaseError(`Query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static async closeConnection(connection: any, type: DatabaseType = DatabaseType.MySQL): Promise<void> {
|
|
126
|
+
if (type === DatabaseType.PostgreSQL) {
|
|
127
|
+
return PostgresConnection.closeConnection(connection);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await connection.end();
|
|
132
|
+
Logger.info('MySQL connection closed successfully');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
Logger.warn(`Error closing MySQL connection: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static async testConnection(options: DatabaseConnectionOptions): Promise<boolean> {
|
|
139
|
+
if (options.type === DatabaseType.PostgreSQL) {
|
|
140
|
+
return PostgresConnection.testConnection(options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let connection: mysql.Connection | null = null;
|
|
144
|
+
try {
|
|
145
|
+
connection = await this.createConnection(options);
|
|
146
|
+
return true;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
Logger.warn(`MySQL connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
149
|
+
return false;
|
|
150
|
+
} finally {
|
|
151
|
+
if (connection) {
|
|
152
|
+
await this.closeConnection(connection);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static extractDatabaseName(url: string): string {
|
|
158
|
+
try {
|
|
159
|
+
const parsedUrl = new URL(url);
|
|
160
|
+
return parsedUrl.pathname.slice(1) || 'default';
|
|
161
|
+
} catch {
|
|
162
|
+
return 'default';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|