@sigma4life/mysql-mcp-server 1.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.
- package/.env.example +35 -0
- package/.github/workflows/ci.yml +18 -0
- package/AUTHENTICATION.md +24 -0
- package/CLAUDE.md +37 -0
- package/CONTRIBUTING.md +19 -0
- package/LICENSE +21 -0
- package/QUERY_ACCESS_SETUP.md +65 -0
- package/README.md +144 -0
- package/SECURITY.md +10 -0
- package/TESTING.md +54 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +197 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/cache.d.ts +11 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +30 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +24 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +63 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/database-url.d.ts +11 -0
- package/dist/core/database-url.d.ts.map +1 -0
- package/dist/core/database-url.js +37 -0
- package/dist/core/database-url.js.map +1 -0
- package/dist/core/database-url.test.d.ts +2 -0
- package/dist/core/database-url.test.d.ts.map +1 -0
- package/dist/core/database-url.test.js +22 -0
- package/dist/core/database-url.test.js.map +1 -0
- package/dist/core/logger.d.ts +3 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +17 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/mcp.d.ts +14 -0
- package/dist/core/mcp.d.ts.map +1 -0
- package/dist/core/mcp.js +23 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/query-safety.d.ts +10 -0
- package/dist/core/query-safety.d.ts.map +1 -0
- package/dist/core/query-safety.js +50 -0
- package/dist/core/query-safety.js.map +1 -0
- package/dist/core/query-safety.test.d.ts +2 -0
- package/dist/core/query-safety.test.d.ts.map +1 -0
- package/dist/core/query-safety.test.js +30 -0
- package/dist/core/query-safety.test.js.map +1 -0
- package/dist/core/schema-types.d.ts +53 -0
- package/dist/core/schema-types.d.ts.map +1 -0
- package/dist/core/schema-types.js +2 -0
- package/dist/core/schema-types.js.map +1 -0
- package/dist/core/security/access-control.d.ts +32 -0
- package/dist/core/security/access-control.d.ts.map +1 -0
- package/dist/core/security/access-control.js +244 -0
- package/dist/core/security/access-control.js.map +1 -0
- package/dist/core/security/config-loader.d.ts +20 -0
- package/dist/core/security/config-loader.d.ts.map +1 -0
- package/dist/core/security/config-loader.js +227 -0
- package/dist/core/security/config-loader.js.map +1 -0
- package/dist/core/security/types.d.ts +64 -0
- package/dist/core/security/types.d.ts.map +1 -0
- package/dist/core/security/types.js +28 -0
- package/dist/core/security/types.js.map +1 -0
- package/dist/core/sql-parser.d.ts +23 -0
- package/dist/core/sql-parser.d.ts.map +1 -0
- package/dist/core/sql-parser.js +460 -0
- package/dist/core/sql-parser.js.map +1 -0
- package/dist/core/sql-parser.test.d.ts +2 -0
- package/dist/core/sql-parser.test.d.ts.map +1 -0
- package/dist/core/sql-parser.test.js +21 -0
- package/dist/core/sql-parser.test.js.map +1 -0
- package/dist/handlers/accessible-schema.d.ts +53 -0
- package/dist/handlers/accessible-schema.d.ts.map +1 -0
- package/dist/handlers/accessible-schema.js +192 -0
- package/dist/handlers/accessible-schema.js.map +1 -0
- package/dist/handlers/data.d.ts +17 -0
- package/dist/handlers/data.d.ts.map +1 -0
- package/dist/handlers/data.js +30 -0
- package/dist/handlers/data.js.map +1 -0
- package/dist/handlers/relationships.d.ts +14 -0
- package/dist/handlers/relationships.d.ts.map +1 -0
- package/dist/handlers/relationships.js +77 -0
- package/dist/handlers/relationships.js.map +1 -0
- package/dist/handlers/schema.d.ts +14 -0
- package/dist/handlers/schema.d.ts.map +1 -0
- package/dist/handlers/schema.js +104 -0
- package/dist/handlers/schema.js.map +1 -0
- package/dist/handlers/search.d.ts +14 -0
- package/dist/handlers/search.d.ts.map +1 -0
- package/dist/handlers/search.js +18 -0
- package/dist/handlers/search.js.map +1 -0
- package/dist/handlers/validation.d.ts +32 -0
- package/dist/handlers/validation.d.ts.map +1 -0
- package/dist/handlers/validation.js +116 -0
- package/dist/handlers/validation.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/dist/mysql/connection.d.ts +15 -0
- package/dist/mysql/connection.d.ts.map +1 -0
- package/dist/mysql/connection.js +57 -0
- package/dist/mysql/connection.js.map +1 -0
- package/dist/mysql/identifiers.d.ts +3 -0
- package/dist/mysql/identifiers.d.ts.map +1 -0
- package/dist/mysql/identifiers.js +10 -0
- package/dist/mysql/identifiers.js.map +1 -0
- package/dist/mysql/identifiers.test.d.ts +2 -0
- package/dist/mysql/identifiers.test.d.ts.map +1 -0
- package/dist/mysql/identifiers.test.js +11 -0
- package/dist/mysql/identifiers.test.js.map +1 -0
- package/dist/mysql/queries.d.ts +49 -0
- package/dist/mysql/queries.d.ts.map +1 -0
- package/dist/mysql/queries.js +206 -0
- package/dist/mysql/queries.js.map +1 -0
- package/docs/clients/claude-code.md +28 -0
- package/docs/clients/claude-desktop.md +35 -0
- package/docs/clients/codex.md +28 -0
- package/docs/clients/cursor.md +26 -0
- package/docs/clients/opencode.md +35 -0
- package/docs/clients/vscode.md +25 -0
- package/package.json +49 -0
- package/query-access.example.json +21 -0
- package/src/cli.ts +221 -0
- package/src/core/cache.ts +41 -0
- package/src/core/config.ts +97 -0
- package/src/core/database-url.test.ts +28 -0
- package/src/core/database-url.ts +47 -0
- package/src/core/logger.ts +24 -0
- package/src/core/mcp.ts +23 -0
- package/src/core/query-safety.test.ts +36 -0
- package/src/core/query-safety.ts +63 -0
- package/src/core/schema-types.ts +58 -0
- package/src/core/security/access-control.ts +321 -0
- package/src/core/security/config-loader.ts +315 -0
- package/src/core/security/types.ts +114 -0
- package/src/core/sql-parser.test.ts +37 -0
- package/src/core/sql-parser.ts +572 -0
- package/src/handlers/accessible-schema.ts +314 -0
- package/src/handlers/data.ts +66 -0
- package/src/handlers/relationships.ts +114 -0
- package/src/handlers/schema.ts +154 -0
- package/src/handlers/search.ts +34 -0
- package/src/handlers/validation.ts +165 -0
- package/src/index.ts +337 -0
- package/src/mysql/connection.ts +68 -0
- package/src/mysql/identifiers.test.ts +12 -0
- package/src/mysql/identifiers.ts +10 -0
- package/src/mysql/queries.ts +285 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { appConfig } from '../core/config.js';
|
|
2
|
+
import { db } from './connection.js';
|
|
3
|
+
import { likePattern } from './identifiers.js';
|
|
4
|
+
export async function listTables(tableNames) {
|
|
5
|
+
const tableFilter = tableNames?.length
|
|
6
|
+
? 'AND t.TABLE_NAME IN (:tableNames)'
|
|
7
|
+
: '';
|
|
8
|
+
return db.query(`
|
|
9
|
+
SELECT
|
|
10
|
+
t.TABLE_SCHEMA AS schemaName,
|
|
11
|
+
t.TABLE_NAME AS tableName,
|
|
12
|
+
t.TABLE_TYPE AS tableType,
|
|
13
|
+
t.CREATE_TIME AS createDate,
|
|
14
|
+
t.TABLE_ROWS AS rowCount
|
|
15
|
+
FROM information_schema.TABLES t
|
|
16
|
+
WHERE t.TABLE_SCHEMA = :database
|
|
17
|
+
AND t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
|
|
18
|
+
${tableFilter}
|
|
19
|
+
ORDER BY t.TABLE_NAME
|
|
20
|
+
`, { database: appConfig.db.name, tableNames });
|
|
21
|
+
}
|
|
22
|
+
export async function getColumns(tableNames) {
|
|
23
|
+
const tableFilter = tableNames?.length
|
|
24
|
+
? 'AND c.TABLE_NAME IN (:tableNames)'
|
|
25
|
+
: '';
|
|
26
|
+
return db.query(`
|
|
27
|
+
SELECT
|
|
28
|
+
c.COLUMN_NAME AS name,
|
|
29
|
+
c.ORDINAL_POSITION AS ordinal,
|
|
30
|
+
c.COLUMN_TYPE AS dataType,
|
|
31
|
+
CASE WHEN c.IS_NULLABLE = 'YES' THEN true ELSE false END AS nullable,
|
|
32
|
+
CASE WHEN c.EXTRA LIKE '%auto_increment%' THEN true ELSE false END AS isIdentity,
|
|
33
|
+
CASE WHEN c.EXTRA LIKE '%VIRTUAL GENERATED%' OR c.EXTRA LIKE '%STORED GENERATED%' THEN true ELSE false END AS isComputed,
|
|
34
|
+
c.COLUMN_DEFAULT AS defaultValue,
|
|
35
|
+
c.COLUMN_COMMENT AS description,
|
|
36
|
+
CASE WHEN kcu.CONSTRAINT_NAME = 'PRIMARY' THEN true ELSE false END AS isPrimaryKey,
|
|
37
|
+
CASE WHEN fk.CONSTRAINT_NAME IS NOT NULL THEN true ELSE false END AS isForeignKey,
|
|
38
|
+
c.TABLE_SCHEMA AS \`schema\`,
|
|
39
|
+
c.TABLE_NAME AS tableName
|
|
40
|
+
FROM information_schema.COLUMNS c
|
|
41
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu
|
|
42
|
+
ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
|
|
43
|
+
AND kcu.TABLE_NAME = c.TABLE_NAME
|
|
44
|
+
AND kcu.COLUMN_NAME = c.COLUMN_NAME
|
|
45
|
+
AND kcu.CONSTRAINT_NAME = 'PRIMARY'
|
|
46
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE fk
|
|
47
|
+
ON fk.TABLE_SCHEMA = c.TABLE_SCHEMA
|
|
48
|
+
AND fk.TABLE_NAME = c.TABLE_NAME
|
|
49
|
+
AND fk.COLUMN_NAME = c.COLUMN_NAME
|
|
50
|
+
AND fk.REFERENCED_TABLE_NAME IS NOT NULL
|
|
51
|
+
WHERE c.TABLE_SCHEMA = :database
|
|
52
|
+
${tableFilter}
|
|
53
|
+
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION
|
|
54
|
+
`, { database: appConfig.db.name, tableNames });
|
|
55
|
+
}
|
|
56
|
+
export async function getPrimaryKeys(tableNames) {
|
|
57
|
+
const tableFilter = tableNames?.length
|
|
58
|
+
? 'AND kcu.TABLE_NAME IN (:tableNames)'
|
|
59
|
+
: '';
|
|
60
|
+
return db.query(`
|
|
61
|
+
SELECT
|
|
62
|
+
kcu.TABLE_NAME AS tableName,
|
|
63
|
+
kcu.CONSTRAINT_NAME AS constraintName,
|
|
64
|
+
GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS columns
|
|
65
|
+
FROM information_schema.KEY_COLUMN_USAGE kcu
|
|
66
|
+
WHERE kcu.TABLE_SCHEMA = :database
|
|
67
|
+
AND kcu.CONSTRAINT_NAME = 'PRIMARY'
|
|
68
|
+
${tableFilter}
|
|
69
|
+
GROUP BY kcu.TABLE_NAME, kcu.CONSTRAINT_NAME
|
|
70
|
+
`, { database: appConfig.db.name, tableNames });
|
|
71
|
+
}
|
|
72
|
+
export async function getForeignKeys(tableNames) {
|
|
73
|
+
const tableFilter = tableNames?.length
|
|
74
|
+
? 'AND kcu.TABLE_NAME IN (:tableNames)'
|
|
75
|
+
: '';
|
|
76
|
+
return db.query(`
|
|
77
|
+
SELECT
|
|
78
|
+
kcu.TABLE_NAME AS tableName,
|
|
79
|
+
kcu.CONSTRAINT_NAME AS constraintName,
|
|
80
|
+
kcu.TABLE_SCHEMA AS fromSchema,
|
|
81
|
+
kcu.TABLE_NAME AS fromTable,
|
|
82
|
+
GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS fromColumns,
|
|
83
|
+
kcu.REFERENCED_TABLE_SCHEMA AS toSchema,
|
|
84
|
+
kcu.REFERENCED_TABLE_NAME AS toTable,
|
|
85
|
+
GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS toColumns,
|
|
86
|
+
rc.DELETE_RULE AS onDelete,
|
|
87
|
+
rc.UPDATE_RULE AS onUpdate
|
|
88
|
+
FROM information_schema.KEY_COLUMN_USAGE kcu
|
|
89
|
+
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
90
|
+
ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
|
91
|
+
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
|
92
|
+
WHERE kcu.TABLE_SCHEMA = :database
|
|
93
|
+
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
94
|
+
${tableFilter}
|
|
95
|
+
GROUP BY
|
|
96
|
+
kcu.TABLE_NAME,
|
|
97
|
+
kcu.CONSTRAINT_NAME,
|
|
98
|
+
kcu.TABLE_SCHEMA,
|
|
99
|
+
kcu.REFERENCED_TABLE_SCHEMA,
|
|
100
|
+
kcu.REFERENCED_TABLE_NAME,
|
|
101
|
+
rc.DELETE_RULE,
|
|
102
|
+
rc.UPDATE_RULE
|
|
103
|
+
`, { database: appConfig.db.name, tableNames });
|
|
104
|
+
}
|
|
105
|
+
export async function getIndexes(tableNames) {
|
|
106
|
+
const tableFilter = tableNames?.length
|
|
107
|
+
? 'AND s.TABLE_NAME IN (:tableNames)'
|
|
108
|
+
: '';
|
|
109
|
+
return db.query(`
|
|
110
|
+
SELECT
|
|
111
|
+
s.TABLE_NAME AS tableName,
|
|
112
|
+
s.INDEX_NAME AS name,
|
|
113
|
+
s.INDEX_TYPE AS type,
|
|
114
|
+
CASE WHEN s.NON_UNIQUE = 0 THEN true ELSE false END AS isUnique,
|
|
115
|
+
CASE WHEN s.INDEX_NAME = 'PRIMARY' THEN true ELSE false END AS isPrimaryKey,
|
|
116
|
+
GROUP_CONCAT(s.COLUMN_NAME ORDER BY s.SEQ_IN_INDEX SEPARATOR ',') AS columns
|
|
117
|
+
FROM information_schema.STATISTICS s
|
|
118
|
+
WHERE s.TABLE_SCHEMA = :database
|
|
119
|
+
${tableFilter}
|
|
120
|
+
GROUP BY s.TABLE_NAME, s.INDEX_NAME, s.INDEX_TYPE, s.NON_UNIQUE
|
|
121
|
+
`, { database: appConfig.db.name, tableNames });
|
|
122
|
+
}
|
|
123
|
+
export async function getStatistics(tableNames) {
|
|
124
|
+
const tableFilter = tableNames?.length
|
|
125
|
+
? 'AND t.TABLE_NAME IN (:tableNames)'
|
|
126
|
+
: '';
|
|
127
|
+
return db.query(`
|
|
128
|
+
SELECT
|
|
129
|
+
t.TABLE_NAME AS tableName,
|
|
130
|
+
t.TABLE_ROWS AS rowCount,
|
|
131
|
+
ROUND(((t.DATA_LENGTH + t.INDEX_LENGTH) / 1024), 0) AS totalSizeKB,
|
|
132
|
+
ROUND((t.DATA_LENGTH / 1024), 0) AS usedSizeKB
|
|
133
|
+
FROM information_schema.TABLES t
|
|
134
|
+
WHERE t.TABLE_SCHEMA = :database
|
|
135
|
+
${tableFilter}
|
|
136
|
+
`, { database: appConfig.db.name, tableNames });
|
|
137
|
+
}
|
|
138
|
+
export async function findTables(pattern, hasColumn) {
|
|
139
|
+
const params = {
|
|
140
|
+
database: appConfig.db.name,
|
|
141
|
+
pattern: likePattern(pattern),
|
|
142
|
+
hasColumn: likePattern(hasColumn),
|
|
143
|
+
};
|
|
144
|
+
return db.query(`
|
|
145
|
+
SELECT DISTINCT
|
|
146
|
+
t.TABLE_SCHEMA AS schemaName,
|
|
147
|
+
t.TABLE_NAME AS tableName,
|
|
148
|
+
t.TABLE_ROWS AS rowCount,
|
|
149
|
+
t.CREATE_TIME AS createDate
|
|
150
|
+
FROM information_schema.TABLES t
|
|
151
|
+
${hasColumn ? `JOIN information_schema.COLUMNS c
|
|
152
|
+
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME` : ''}
|
|
153
|
+
WHERE t.TABLE_SCHEMA = :database
|
|
154
|
+
AND t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
|
|
155
|
+
${pattern ? 'AND t.TABLE_NAME LIKE :pattern' : ''}
|
|
156
|
+
${hasColumn ? 'AND c.COLUMN_NAME LIKE :hasColumn' : ''}
|
|
157
|
+
ORDER BY t.TABLE_NAME
|
|
158
|
+
LIMIT 100
|
|
159
|
+
`, params);
|
|
160
|
+
}
|
|
161
|
+
export async function searchObjects(search, type) {
|
|
162
|
+
const pattern = likePattern(search) || `%${search}%`;
|
|
163
|
+
const includeTables = !type || type === 'table';
|
|
164
|
+
const includeColumns = !type || type === 'column';
|
|
165
|
+
const parts = [];
|
|
166
|
+
if (includeTables) {
|
|
167
|
+
parts.push(`
|
|
168
|
+
SELECT t.TABLE_SCHEMA AS schemaName, t.TABLE_NAME AS tableName, NULL AS columnName
|
|
169
|
+
FROM information_schema.TABLES t
|
|
170
|
+
WHERE t.TABLE_SCHEMA = :database
|
|
171
|
+
AND t.TABLE_NAME LIKE :pattern`);
|
|
172
|
+
}
|
|
173
|
+
if (includeColumns) {
|
|
174
|
+
parts.push(`
|
|
175
|
+
SELECT c.TABLE_SCHEMA AS schemaName, c.TABLE_NAME AS tableName, c.COLUMN_NAME AS columnName
|
|
176
|
+
FROM information_schema.COLUMNS c
|
|
177
|
+
WHERE c.TABLE_SCHEMA = :database
|
|
178
|
+
AND c.COLUMN_NAME LIKE :pattern`);
|
|
179
|
+
}
|
|
180
|
+
if (parts.length === 0) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return db.query(`${parts.join('\nUNION ALL\n')}\nORDER BY tableName, columnName\nLIMIT 100`, { database: appConfig.db.name, pattern });
|
|
184
|
+
}
|
|
185
|
+
export async function getRelationships() {
|
|
186
|
+
return db.query(`
|
|
187
|
+
SELECT
|
|
188
|
+
kcu.TABLE_SCHEMA AS fromSchema,
|
|
189
|
+
kcu.TABLE_NAME AS fromTable,
|
|
190
|
+
kcu.COLUMN_NAME AS fromColumn,
|
|
191
|
+
kcu.REFERENCED_TABLE_SCHEMA AS toSchema,
|
|
192
|
+
kcu.REFERENCED_TABLE_NAME AS toTable,
|
|
193
|
+
kcu.REFERENCED_COLUMN_NAME AS toColumn,
|
|
194
|
+
kcu.CONSTRAINT_NAME AS constraintName,
|
|
195
|
+
rc.DELETE_RULE AS deleteAction,
|
|
196
|
+
rc.UPDATE_RULE AS updateAction
|
|
197
|
+
FROM information_schema.KEY_COLUMN_USAGE kcu
|
|
198
|
+
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
199
|
+
ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
|
200
|
+
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
|
201
|
+
WHERE kcu.TABLE_SCHEMA = :database
|
|
202
|
+
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
203
|
+
ORDER BY kcu.TABLE_NAME, kcu.ORDINAL_POSITION
|
|
204
|
+
`, { database: appConfig.db.name });
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/mysql/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Claude Code
|
|
2
|
+
|
|
3
|
+
Add the server as a local stdio MCP:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
claude mcp add --transport stdio mysql \
|
|
7
|
+
--env DATABASE_URL=mysql://mcp_reader:password@localhost:3306/app_db \
|
|
8
|
+
-- npx -y @sigma4life/mysql-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For read-query support, also pass:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
--env SCHEMA_ONLY_MODE=false \
|
|
15
|
+
--env QUERY_ACCESS_CONFIG=/absolute/path/to/query-access.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Check status inside Claude Code:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
/mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Try:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Use mysql to show me the tables in this database.
|
|
28
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Claude Desktop
|
|
2
|
+
|
|
3
|
+
Add this to your Claude Desktop MCP configuration:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"mcpServers": {
|
|
8
|
+
"mysql": {
|
|
9
|
+
"command": "npx",
|
|
10
|
+
"args": ["-y", "@sigma4life/mysql-mcp-server"],
|
|
11
|
+
"env": {
|
|
12
|
+
"DATABASE_URL": "mysql://mcp_reader:password@localhost:3306/app_db",
|
|
13
|
+
"SCHEMA_ONLY_MODE": "true"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Restart Claude Desktop after editing the config.
|
|
21
|
+
|
|
22
|
+
For read-query support, set:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"SCHEMA_ONLY_MODE": "false",
|
|
27
|
+
"QUERY_ACCESS_CONFIG": "/absolute/path/to/query-access.json"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Try:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Show me the schema for customers and orders.
|
|
35
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Codex
|
|
2
|
+
|
|
3
|
+
Add the server as a local stdio MCP:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
codex mcp add mysql \
|
|
7
|
+
--env DATABASE_URL=mysql://mcp_reader:password@localhost:3306/app_db \
|
|
8
|
+
-- npx -y @sigma4life/mysql-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Verify:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
codex mcp list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For read-query support, also pass:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
--env SCHEMA_ONLY_MODE=false \
|
|
21
|
+
--env QUERY_ACCESS_CONFIG=/absolute/path/to/query-access.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Try:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Use mysql to find tables with an email column.
|
|
28
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Cursor
|
|
2
|
+
|
|
3
|
+
Create or update `~/.cursor/mcp.json`:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"mcpServers": {
|
|
8
|
+
"mysql": {
|
|
9
|
+
"command": "npx",
|
|
10
|
+
"args": ["-y", "@sigma4life/mysql-mcp-server"],
|
|
11
|
+
"env": {
|
|
12
|
+
"DATABASE_URL": "mysql://mcp_reader:password@localhost:3306/app_db",
|
|
13
|
+
"SCHEMA_ONLY_MODE": "true"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Restart Cursor after editing the config.
|
|
21
|
+
|
|
22
|
+
Try:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Use mysql to show me the database tables.
|
|
26
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# OpenCode
|
|
2
|
+
|
|
3
|
+
Add a local MCP server to `opencode.json`:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"$schema": "https://opencode.ai/config.json",
|
|
8
|
+
"mcp": {
|
|
9
|
+
"mysql": {
|
|
10
|
+
"type": "local",
|
|
11
|
+
"command": ["npx", "-y", "@sigma4life/mysql-mcp-server"],
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"environment": {
|
|
14
|
+
"DATABASE_URL": "mysql://mcp_reader:password@localhost:3306/app_db",
|
|
15
|
+
"SCHEMA_ONLY_MODE": "true"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For read-query support, add:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"SCHEMA_ONLY_MODE": "false",
|
|
27
|
+
"QUERY_ACCESS_CONFIG": "/absolute/path/to/query-access.json"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Try:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Use the mysql tool to show relationships from orders.
|
|
35
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# VS Code
|
|
2
|
+
|
|
3
|
+
Create `.vscode/mcp.json` in your project:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"servers": {
|
|
8
|
+
"mysql": {
|
|
9
|
+
"type": "stdio",
|
|
10
|
+
"command": "npx",
|
|
11
|
+
"args": ["-y", "@sigma4life/mysql-mcp-server"],
|
|
12
|
+
"env": {
|
|
13
|
+
"DATABASE_URL": "mysql://mcp_reader:password@localhost:3306/app_db",
|
|
14
|
+
"SCHEMA_ONLY_MODE": "true"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Try:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Use mysql to inspect the customers table.
|
|
25
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigma4life/mysql-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for MySQL schema introspection and guarded read-only queries",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mysql-mcp-server": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/cli.js serve",
|
|
14
|
+
"test": "npm run build && node --test dist/**/*.test.js",
|
|
15
|
+
"lint": "eslint src/**/*.ts",
|
|
16
|
+
"format": "prettier --write src/**/*.ts"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"mysql",
|
|
21
|
+
"database",
|
|
22
|
+
"schema",
|
|
23
|
+
"ai"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
29
|
+
"dotenv": "^16.4.7",
|
|
30
|
+
"mysql2": "^3.12.0",
|
|
31
|
+
"node-sql-parser": "^5.3.6",
|
|
32
|
+
"winston": "^3.17.0",
|
|
33
|
+
"zod": "^3.24.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.10.5",
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
38
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
39
|
+
"eslint": "^9.18.0",
|
|
40
|
+
"prettier": "^3.4.2",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"requireExplicitColumns": true,
|
|
3
|
+
"databases": {
|
|
4
|
+
"app_db": {
|
|
5
|
+
"tables": {
|
|
6
|
+
"mode": "whitelist",
|
|
7
|
+
"list": ["customers", "orders", "products", "order_items"],
|
|
8
|
+
"columnAccess": {
|
|
9
|
+
"customers": {
|
|
10
|
+
"mode": "exclusion",
|
|
11
|
+
"columns": ["password_hash", "api_token"]
|
|
12
|
+
},
|
|
13
|
+
"orders": {
|
|
14
|
+
"mode": "inclusion",
|
|
15
|
+
"columns": ["id", "customer_id", "status", "total", "created_at"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { maskDatabaseUrl } from './core/database-url.js';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_DATABASE_URL = 'mysql://mcp_reader:change_me@localhost:3306/app_db';
|
|
8
|
+
|
|
9
|
+
function usage(): void {
|
|
10
|
+
console.log(`mysql-mcp-server
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
mysql-mcp-server Start the MCP stdio server
|
|
14
|
+
mysql-mcp-server serve Start the MCP stdio server
|
|
15
|
+
mysql-mcp-server doctor Check configuration and database connectivity
|
|
16
|
+
mysql-mcp-server init [--force] Create starter .env, query-access.json, and client snippets
|
|
17
|
+
|
|
18
|
+
Environment:
|
|
19
|
+
DATABASE_URL=mysql://user:password@host:3306/database
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeFileIfMissing(filePath: string, content: string, force: boolean): 'created' | 'skipped' {
|
|
24
|
+
if (fs.existsSync(filePath) && !force) {
|
|
25
|
+
return 'skipped';
|
|
26
|
+
}
|
|
27
|
+
fs.writeFileSync(filePath, content);
|
|
28
|
+
return 'created';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function envTemplate(): string {
|
|
32
|
+
return `# One-line setup. Individual DB_* variables may override parts of this URL.
|
|
33
|
+
DATABASE_URL=${DEFAULT_DATABASE_URL}
|
|
34
|
+
|
|
35
|
+
# Safer first-run default: schema tools only. Set false to enable execute_query.
|
|
36
|
+
SCHEMA_ONLY_MODE=true
|
|
37
|
+
|
|
38
|
+
# Required only when SCHEMA_ONLY_MODE=false.
|
|
39
|
+
# QUERY_ACCESS_CONFIG=${path.resolve('query-access.json')}
|
|
40
|
+
|
|
41
|
+
MAX_QUERY_ROWS=100
|
|
42
|
+
QUERY_TIMEOUT_MS=30000
|
|
43
|
+
MCP_SERVER_NAME=mysql-mcp-server
|
|
44
|
+
MCP_SERVER_VERSION=1.0.0
|
|
45
|
+
LOG_LEVEL=info
|
|
46
|
+
LOG_FILE=mcp-server.log
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function queryAccessTemplate(): string {
|
|
51
|
+
return JSON.stringify(
|
|
52
|
+
{
|
|
53
|
+
requireExplicitColumns: true,
|
|
54
|
+
databases: {
|
|
55
|
+
app_db: {
|
|
56
|
+
tables: {
|
|
57
|
+
mode: 'whitelist',
|
|
58
|
+
list: ['customers', 'orders', 'products', 'order_items'],
|
|
59
|
+
columnAccess: {
|
|
60
|
+
customers: {
|
|
61
|
+
mode: 'exclusion',
|
|
62
|
+
columns: ['password_hash', 'api_token'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
null,
|
|
70
|
+
2,
|
|
71
|
+
) + '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function clientSnippetsTemplate(): string {
|
|
75
|
+
return `# mysql-mcp-server client snippets
|
|
76
|
+
|
|
77
|
+
Replace the sample DATABASE_URL with your real MySQL connection string.
|
|
78
|
+
|
|
79
|
+
## Claude Code
|
|
80
|
+
|
|
81
|
+
\`\`\`bash
|
|
82
|
+
claude mcp add --transport stdio mysql \\
|
|
83
|
+
--env DATABASE_URL=${DEFAULT_DATABASE_URL} \\
|
|
84
|
+
-- npx -y @sigma4life/mysql-mcp-server
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
## Codex
|
|
88
|
+
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
codex mcp add mysql \\
|
|
91
|
+
--env DATABASE_URL=${DEFAULT_DATABASE_URL} \\
|
|
92
|
+
-- npx -y @sigma4life/mysql-mcp-server
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
## Claude Desktop
|
|
96
|
+
|
|
97
|
+
\`\`\`json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"mysql": {
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": ["-y", "@sigma4life/mysql-mcp-server"],
|
|
103
|
+
"env": {
|
|
104
|
+
"DATABASE_URL": "${DEFAULT_DATABASE_URL}"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
## OpenCode
|
|
112
|
+
|
|
113
|
+
\`\`\`json
|
|
114
|
+
{
|
|
115
|
+
"$schema": "https://opencode.ai/config.json",
|
|
116
|
+
"mcp": {
|
|
117
|
+
"mysql": {
|
|
118
|
+
"type": "local",
|
|
119
|
+
"command": ["npx", "-y", "@sigma4life/mysql-mcp-server"],
|
|
120
|
+
"enabled": true,
|
|
121
|
+
"environment": {
|
|
122
|
+
"DATABASE_URL": "${DEFAULT_DATABASE_URL}"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
\`\`\`
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runInit(args: string[]): Promise<void> {
|
|
132
|
+
const force = args.includes('--force');
|
|
133
|
+
const files = [
|
|
134
|
+
['.env', envTemplate()],
|
|
135
|
+
['query-access.json', queryAccessTemplate()],
|
|
136
|
+
['mysql-mcp-clients.md', clientSnippetsTemplate()],
|
|
137
|
+
] as const;
|
|
138
|
+
|
|
139
|
+
for (const [file, content] of files) {
|
|
140
|
+
const status = writeFileIfMissing(path.resolve(file), content, force);
|
|
141
|
+
console.log(`${status === 'created' ? 'created' : 'skipped'} ${file}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log('\nNext steps:');
|
|
145
|
+
console.log('1. Edit .env with your MySQL credentials.');
|
|
146
|
+
console.log('2. Run: mysql-mcp-server doctor');
|
|
147
|
+
console.log('3. Add one of the snippets from mysql-mcp-clients.md to your MCP client.');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runDoctor(): Promise<void> {
|
|
151
|
+
console.log('mysql-mcp-server doctor\n');
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const { appConfig } = await import('./core/config.js');
|
|
155
|
+
const { db } = await import('./mysql/connection.js');
|
|
156
|
+
|
|
157
|
+
console.log(`server: ${appConfig.server.name} v${appConfig.server.version}`);
|
|
158
|
+
console.log(`database: ${appConfig.db.user}@${appConfig.db.host}:${appConfig.db.port}/${appConfig.db.name}`);
|
|
159
|
+
console.log(`ssl: ${appConfig.db.ssl ? 'enabled' : 'disabled'}`);
|
|
160
|
+
console.log(`schema-only mode: ${appConfig.server.schemaOnlyMode ? 'enabled' : 'disabled'}`);
|
|
161
|
+
if (process.env.DATABASE_URL) {
|
|
162
|
+
console.log(`DATABASE_URL: ${maskDatabaseUrl(process.env.DATABASE_URL)}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const versionRows = await db.query<any>('SELECT VERSION() AS version');
|
|
166
|
+
console.log(`mysql: ${versionRows[0]?.version || 'connected'}`);
|
|
167
|
+
|
|
168
|
+
const tableRows = await db.query<any>(
|
|
169
|
+
`SELECT COUNT(*) AS tableCount
|
|
170
|
+
FROM information_schema.TABLES
|
|
171
|
+
WHERE TABLE_SCHEMA = :database
|
|
172
|
+
AND TABLE_TYPE IN ('BASE TABLE', 'VIEW')`,
|
|
173
|
+
{ database: appConfig.db.name },
|
|
174
|
+
);
|
|
175
|
+
console.log(`tables/views visible: ${tableRows[0]?.tableCount ?? 0}`);
|
|
176
|
+
|
|
177
|
+
if (!appConfig.server.schemaOnlyMode) {
|
|
178
|
+
if (process.env.QUERY_ACCESS_CONFIG) {
|
|
179
|
+
const { loadAccessControlConfig } = await import('./core/security/access-control.js');
|
|
180
|
+
loadAccessControlConfig();
|
|
181
|
+
console.log('query access config: ok');
|
|
182
|
+
} else {
|
|
183
|
+
console.log('query access config: missing; execute_query will be blocked');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await db.close();
|
|
188
|
+
console.log('\nDoctor passed.');
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('Doctor failed.');
|
|
191
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function main(): Promise<void> {
|
|
197
|
+
const [command = 'serve', ...args] = process.argv.slice(2);
|
|
198
|
+
|
|
199
|
+
switch (command) {
|
|
200
|
+
case 'serve':
|
|
201
|
+
await import('./index.js');
|
|
202
|
+
break;
|
|
203
|
+
case 'doctor':
|
|
204
|
+
await runDoctor();
|
|
205
|
+
break;
|
|
206
|
+
case 'init':
|
|
207
|
+
await runInit(args);
|
|
208
|
+
break;
|
|
209
|
+
case '--help':
|
|
210
|
+
case '-h':
|
|
211
|
+
case 'help':
|
|
212
|
+
usage();
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
console.error(`Unknown command: ${command}\n`);
|
|
216
|
+
usage();
|
|
217
|
+
process.exitCode = 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
main();
|