@imenam/database-mcp 1.0.9

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/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { GuiLauncher, setupLogging } from '@imenam/mcp-gui-interface';
8
+ import { TOOLS, ExecuteQuerySchema, DescribeTableSchema, ExecuteWriteSchema } from './tools.js';
9
+ import { executeQuery, listTables, describeTable, isProxyMode } from './db.js';
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ setupLogging({ processLabel: 'MASTER', logDir: process.env.MCP_LOG_DIR });
12
+ const DEFAULT_MAX_ROWS = parseInt(process.env.MYSQL_MCP_DEFAULT_MAX_ROWS ?? '10');
13
+ const DEFAULT_MAX_CELL_LENGTH = parseInt(process.env.MYSQL_MCP_DEFAULT_MAX_CELL_LENGTH ?? '50');
14
+ const READ_ONLY_PREFIXES = ['SELECT', 'SHOW', 'DESCRIBE', 'EXPLAIN', 'WITH'];
15
+ function applyLimits(rows, maxRows, maxCellLength) {
16
+ const limited = rows.slice(0, maxRows);
17
+ const truncated = limited.map(row => {
18
+ const newRow = {};
19
+ for (const [key, value] of Object.entries(row)) {
20
+ if (typeof value === 'string' && value.length > maxCellLength) {
21
+ newRow[key] = value.slice(0, maxCellLength) + ' [truncated]';
22
+ }
23
+ else {
24
+ newRow[key] = value;
25
+ }
26
+ }
27
+ return newRow;
28
+ });
29
+ return {
30
+ rows: truncated,
31
+ meta: {
32
+ total_returned: truncated.length,
33
+ was_row_limited: rows.length > maxRows,
34
+ },
35
+ };
36
+ }
37
+ function isReadOnly(sql) {
38
+ const normalized = sql.trim().toUpperCase();
39
+ return READ_ONLY_PREFIXES.some((prefix) => normalized.startsWith(prefix));
40
+ }
41
+ const launcher = new GuiLauncher({
42
+ guiPath: path.join(__dirname, 'gui.js'),
43
+ onMessage: (msg) => {
44
+ if (msg.type === 'STOP')
45
+ process.exit(0);
46
+ },
47
+ });
48
+ const server = new Server({
49
+ name: 'mysql-mcp',
50
+ version: '1.0.0',
51
+ }, {
52
+ capabilities: {
53
+ tools: {},
54
+ },
55
+ });
56
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
57
+ return { tools: TOOLS };
58
+ });
59
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
60
+ const { name, arguments: args } = request.params;
61
+ try {
62
+ switch (name) {
63
+ case 'list_tables': {
64
+ const tables = await listTables();
65
+ return {
66
+ content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }],
67
+ };
68
+ }
69
+ case 'describe_table': {
70
+ const { table_name } = DescribeTableSchema.parse(args);
71
+ const schema = await describeTable(table_name);
72
+ return {
73
+ content: [{ type: 'text', text: JSON.stringify(schema, null, 2) }],
74
+ };
75
+ }
76
+ case 'execute_query': {
77
+ const { sql, max_rows, max_cell_length } = ExecuteQuerySchema.parse(args);
78
+ if (!isReadOnly(sql)) {
79
+ throw new Error('Only read-only SELECT statements are allowed with execute_query. Use execute_write for write operations.');
80
+ }
81
+ const rows = await executeQuery(sql);
82
+ const result = applyLimits(rows, max_rows ?? DEFAULT_MAX_ROWS, max_cell_length ?? DEFAULT_MAX_CELL_LENGTH);
83
+ return {
84
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
85
+ };
86
+ }
87
+ case 'execute_write': {
88
+ if (isProxyMode()) {
89
+ throw new Error('execute_write est désactivé en mode proxy HTTP (API en lecture seule).');
90
+ }
91
+ if (process.env.MYSQL_MCP_ALLOW_WRITE?.trim().toLowerCase() !== 'true') {
92
+ throw new Error('Write operations are disabled: set MYSQL_MCP_ALLOW_WRITE=true in the MCP environment configuration to enable them.');
93
+ }
94
+ const { sql, max_rows, max_cell_length } = ExecuteWriteSchema.parse(args);
95
+ if (isReadOnly(sql)) {
96
+ throw new Error('Use execute_query for SELECT statements, not execute_write.');
97
+ }
98
+ const rawResult = await executeQuery(sql);
99
+ const result = Array.isArray(rawResult)
100
+ ? applyLimits(rawResult, max_rows ?? DEFAULT_MAX_ROWS, max_cell_length ?? DEFAULT_MAX_CELL_LENGTH)
101
+ : rawResult;
102
+ return {
103
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
104
+ };
105
+ }
106
+ default:
107
+ throw new Error(`Unknown tool: ${name}`);
108
+ }
109
+ }
110
+ catch (error) {
111
+ const message = error instanceof Error ? error.message : 'An unknown error occurred';
112
+ return {
113
+ isError: true,
114
+ content: [{ type: 'text', text: message }],
115
+ };
116
+ }
117
+ });
118
+ async function main() {
119
+ if (!process.env.PROXY_URL) {
120
+ console.error('[MASTER] PROXY_URL non défini — GUI désactivée.');
121
+ }
122
+ else {
123
+ launcher.start();
124
+ }
125
+ const transport = new StdioServerTransport();
126
+ await server.connect(transport);
127
+ const dbType = process.env.MYSQL_MCP_DB_TYPE ?? 'mysql';
128
+ console.error(`DB MCP server running on stdio (db_type: ${dbType})`);
129
+ }
130
+ main().catch((error) => {
131
+ console.error('Fatal error in main():', error);
132
+ process.exit(1);
133
+ });
package/dist/tools.js ADDED
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ export const ExecuteQuerySchema = z.object({
3
+ sql: z
4
+ .string()
5
+ .min(1)
6
+ .describe('The SQL SELECT query to execute. Only SELECT statements are allowed.'),
7
+ max_rows: z.number().int().positive().optional(),
8
+ max_cell_length: z.number().int().positive().optional(),
9
+ });
10
+ export const DescribeTableSchema = z.object({
11
+ table_name: z.string().min(1).describe('The name of the table to describe.'),
12
+ });
13
+ export const ExecuteWriteSchema = z.object({
14
+ sql: z
15
+ .string()
16
+ .min(1)
17
+ .describe('The SQL write statement to execute (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER). Use with caution.'),
18
+ max_rows: z.number().int().positive().optional(),
19
+ max_cell_length: z.number().int().positive().optional(),
20
+ });
21
+ export const TOOLS = [
22
+ {
23
+ name: 'list_tables',
24
+ description: 'List all tables available in the connected database.',
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {},
28
+ required: [],
29
+ },
30
+ },
31
+ {
32
+ name: 'describe_table',
33
+ description: 'Describe the schema of a specific table: column names, types, nullability, keys, and defaults.',
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ table_name: {
38
+ type: 'string',
39
+ description: 'The name of the table to describe.',
40
+ },
41
+ },
42
+ required: ['table_name'],
43
+ },
44
+ },
45
+ {
46
+ name: 'execute_query',
47
+ description: 'Execute a read-only SQL SELECT query against the connected database and return the results. Results are limited by default to avoid polluting the model context. Only increase max_rows or max_cell_length if you explicitly need more data.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {
51
+ sql: {
52
+ type: 'string',
53
+ description: 'The SQL SELECT query to execute. Only SELECT statements are allowed.',
54
+ },
55
+ max_rows: {
56
+ type: 'integer',
57
+ description: 'Maximum number of rows to return. Defaults to 10. Only increase if you explicitly need more results.',
58
+ },
59
+ max_cell_length: {
60
+ type: 'integer',
61
+ description: 'Maximum character length per cell value. Content exceeding this limit is truncated with "[truncated]". Defaults to 50.',
62
+ },
63
+ },
64
+ required: ['sql'],
65
+ },
66
+ },
67
+ {
68
+ name: 'execute_write',
69
+ description: 'Execute a write SQL statement (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER) against the connected database. This action is irreversible — always confirm with the user before calling this tool. Requires MYSQL_MCP_ALLOW_WRITE=true to be set in the environment, otherwise the tool will return an error. If the statement returns rows, results are limited by default to avoid polluting the model context.',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ sql: {
74
+ type: 'string',
75
+ description: 'The SQL write statement to execute. Use with caution as this modifies data.',
76
+ },
77
+ max_rows: {
78
+ type: 'integer',
79
+ description: 'Maximum number of rows to return (if the statement returns rows). Defaults to 10. Only increase if you explicitly need more results.',
80
+ },
81
+ max_cell_length: {
82
+ type: 'integer',
83
+ description: 'Maximum character length per cell value. Content exceeding this limit is truncated with "[truncated]". Defaults to 50.',
84
+ },
85
+ },
86
+ required: ['sql'],
87
+ },
88
+ },
89
+ ];
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@imenam/database-mcp",
3
+ "version": "1.0.9",
4
+ "main": "dist/index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "build": "tsc && npm run build:gui",
8
+ "build:gui": "cd public && npx vite build",
9
+ "start": "node dist/index.js",
10
+ "dev": "concurrently -n server,gui -c blue,green \"npm run dev:server\" \"npm run dev:gui\"",
11
+ "dev:server": "tsx src/index.ts",
12
+ "dev:gui": "cd public && npx vite",
13
+ "release": "npm run build && npm version patch && npm publish --access public && git push origin master --follow-tags"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "mysql",
19
+ "database",
20
+ "ai",
21
+ "agent",
22
+ "cursor"
23
+ ],
24
+ "author": "",
25
+ "files": [
26
+ "dist",
27
+ "public/dist",
28
+ "README.md"
29
+ ],
30
+ "license": "ISC",
31
+ "description": "A Model Context Protocol server to interact with a MySQL database",
32
+ "dependencies": {
33
+ "@codemirror/autocomplete": "^6.20.1",
34
+ "@codemirror/commands": "^6.10.3",
35
+ "@codemirror/lang-sql": "^6.10.0",
36
+ "@codemirror/language": "^6.12.2",
37
+ "@codemirror/state": "^6.6.0",
38
+ "@codemirror/theme-one-dark": "^6.1.3",
39
+ "@codemirror/view": "^6.40.0",
40
+ "@imenam/mcp-gui-interface": "^1.0.5",
41
+ "@modelcontextprotocol/sdk": "^1.27.1",
42
+ "better-sqlite3": "^12.8.0",
43
+ "body-parser": "^2.2.2",
44
+ "dotenv": "^17.3.1",
45
+ "express": "^5.2.1",
46
+ "mysql2": "^3.18.2",
47
+ "pg": "^8.20.0",
48
+ "react": "^19.2.4",
49
+ "react-dom": "^19.2.4",
50
+ "zod": "^4.3.6"
51
+ },
52
+ "devDependencies": {
53
+ "@types/better-sqlite3": "^7.6.13",
54
+ "@types/body-parser": "^1.19.6",
55
+ "@types/express": "^5.0.6",
56
+ "@types/node": "^25.3.3",
57
+ "@types/pg": "^8.18.0",
58
+ "@types/react": "^19.2.14",
59
+ "@types/react-dom": "^19.2.3",
60
+ "@vitejs/plugin-react": "^6.0.1",
61
+ "concurrently": "^9.2.1",
62
+ "tsx": "^4.21.0",
63
+ "typescript": "^5.9.3",
64
+ "vite": "^8.0.0"
65
+ },
66
+ "type": "module",
67
+ "bin": {
68
+ "databaseMcp": "./dist/index.js"
69
+ }
70
+ }