From b3aa070e9e82a1ddbef099c816a5fe2ec08213d6 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 14:03:16 +0800 Subject: [PATCH 01/11] Added MCP Server for MySQL --- package.json | 5 +- src/mysql/README.md | 53 +++++ src/mysql/index.js | 184 ++++++++++++++++ src/mysql/index.ts | 207 ++++++++++++++++++ src/mysql/package.json | 30 +++ src/mysql/pnpm-lock.yaml | 440 +++++++++++++++++++++++++++++++++++++++ src/mysql/tsconfig.json | 10 + 7 files changed, 927 insertions(+), 2 deletions(-) create mode 100644 src/mysql/README.md create mode 100644 src/mysql/index.js create mode 100644 src/mysql/index.ts create mode 100644 src/mysql/package.json create mode 100644 src/mysql/pnpm-lock.yaml create mode 100644 src/mysql/tsconfig.json diff --git a/package.json b/package.json index 0203ca98..1e7077a3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@modelcontextprotocol/server-memory": "*", "@modelcontextprotocol/server-filesystem": "*", "@modelcontextprotocol/server-everart": "*", - "@modelcontextprotocol/server-sequential-thinking": "*" + "@modelcontextprotocol/server-sequential-thinking": "*", + "@modelcontextprotocol/server-mysql": "*" } -} \ No newline at end of file +} diff --git a/src/mysql/README.md b/src/mysql/README.md new file mode 100644 index 00000000..56fd7d14 --- /dev/null +++ b/src/mysql/README.md @@ -0,0 +1,53 @@ +# MySQL + +A Model Context Protocol server that provides read-only access to MySQL databases. This server enables LLMs to inspect database schemas and execute read-only queries. + +## Components + +### Tools + +- **query** + - Execute read-only SQL queries against the connected database + - Input: `sql` (string): The SQL query to execute + - All queries are executed within a READ ONLY transaction + +### Resources + +The server provides schema information for each table in the database: + +- **Table Schemas** + - JSON schema information for each table + - Includes column names and data types + - Automatically discovered from database metadata + +## Usage with Claude Desktop + +To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "mysql": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-mysql", + ], + "env": { + "MYSQL_HOST": "127.0.0.1", + "MYSQL_PORT": "33067", + "MYSQL_USER": "root", + "MYSQL_PASS": "", + "MYSQL_DB": "db_name" + } + + } + } +} +``` + +Replace `/db_name` with your database name or leave it blank to retrieve all databases. + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/mysql/index.js b/src/mysql/index.js new file mode 100644 index 00000000..b2992e25 --- /dev/null +++ b/src/mysql/index.js @@ -0,0 +1,184 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import mysql from "mysql"; + +const server = new Server( + { + name: "example-servers/mysql", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + +const MYSQL_HOST = process.env.MYSQL_HOST || "127.0.0.1"; +const MYSQL_PORT = process.env.MYSQL_PORT || "3306"; +const MYSQL_USER = process.env.MYSQL_USER || "root"; +const MYSQL_PASS = process.env.MYSQL_PASS || ""; +const MYSQL_DB = process.env.MYSQL_DB || ""; + +const pool = mysql.createPool({ + connectionLimit: 10, + host: MYSQL_HOST, + port: MYSQL_PORT, + user: MYSQL_USER, + password: MYSQL_PASS, + database: MYSQL_DB, +}); + +const SCHEMA_PATH = "schema"; + +server.setRequestHandler(ListResourcesRequestSchema, async () => { + return new Promise((resolve, reject) => { + pool.query( + "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", + (error, results) => { + if (error) reject(error); + resolve({ + resources: results.map((row) => ({ + uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl) + .href, + mimeType: "application/json", + name: `"${row.table_name}" database schema`, + })), + }); + }, + ); + }); +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const resourceUrl = new URL(request.params.uri); + + const pathComponents = resourceUrl.pathname.split("/"); + const schema = pathComponents.pop(); + const tableName = pathComponents.pop(); + + if (schema !== SCHEMA_PATH) { + throw new Error("Invalid resource URI"); + } + + return new Promise((resolve, reject) => { + pool.query( + "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", + [tableName], + (error, results) => { + if (error) reject(error); + resolve({ + contents: [ + { + uri: request.params.uri, + mimeType: "application/json", + text: JSON.stringify(results, null, 2), + }, + ], + }); + }, + ); + }); +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "mysql_query", + description: "Run a read-only MySQL query", + inputSchema: { + type: "object", + properties: { + sql: { type: "string" }, + }, + }, + }, + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "mysql_query") { + const sql = request.params.arguments?.sql; + + return new Promise((resolve, reject) => { + pool.getConnection((err, connection) => { + if (err) reject(err); + + // @INFO: Set session to read only BEFORE beginning the transaction + connection.query("SET SESSION TRANSACTION READ ONLY", (err) => { + if (err) { + connection.release(); + reject(err); + return; + } + + connection.beginTransaction((err) => { + if (err) { + connection.release(); + reject(err); + return; + } + + connection.query(sql, (error, results) => { + if (error) { + connection.rollback(() => { + connection.release(); + reject(error); + }); + return; + } + + connection.rollback(() => { + // @INFO: Reset the transaction mode back to default before releasing + connection.query( + "SET SESSION TRANSACTION READ WRITE", + (err) => { + connection.release(); + if (err) { + console.warn("Failed to reset transaction mode:", err); + } + resolve({ + content: [ + { + type: "text", + text: JSON.stringify(results, null, 2), + }, + ], + isError: false, + }); + }, + ); + }); + }); + }); + }); + }); + }); + } + throw new Error(`Unknown tool: ${request.params.name}`); +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +process.on("SIGINT", () => { + pool.end((err) => { + if (err) console.error("Error closing pool:", err); + process.exit(err ? 1 : 0); + }); +}); + +runServer().catch(console.error); diff --git a/src/mysql/index.ts b/src/mysql/index.ts new file mode 100644 index 00000000..7d14aa47 --- /dev/null +++ b/src/mysql/index.ts @@ -0,0 +1,207 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import mysql, { MysqlError, PoolConnection, OkPacket } from "mysql"; + +type MySQLErrorType = MysqlError | null; + +interface TableRow { + table_name: string; +} + +interface ColumnRow { + column_name: string; + data_type: string; +} + +type QueryResult = OkPacket | any[] | any; +const server = new Server( + { + name: "example-servers/mysql", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + +const MYSQL_HOST = process.env.MYSQL_HOST || "127.0.0.1"; +const MYSQL_PORT = process.env.MYSQL_PORT || "3306"; +const MYSQL_USER = process.env.MYSQL_USER || "root"; +const MYSQL_PASS = process.env.MYSQL_PASS || ""; +const MYSQL_DB = process.env.MYSQL_DB || ""; + +const pool = mysql.createPool({ + connectionLimit: 10, + host: MYSQL_HOST, + port: MYSQL_PORT, + user: MYSQL_USER, + password: MYSQL_PASS, + database: MYSQL_DB, +}); + +const SCHEMA_PATH = "schema"; + +server.setRequestHandler(ListResourcesRequestSchema, async () => { + return new Promise((resolve, reject) => { + pool.query( + "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", + (error: MySQLErrorType, results: TableRow[]) => { + if (error) reject(error); + resolve({ + resources: results.map((row: TableRow) => ({ + uri: new URL( + `${row.table_name}/${SCHEMA_PATH}`, + `${MYSQL_HOST}:${MYSQL_PORT}`, + ).href, + mimeType: "application/json", + name: `"${row.table_name}" database schema`, + })), + }); + }, + ); + }); +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const resourceUrl = new URL(request.params.uri); + + const pathComponents = resourceUrl.pathname.split("/"); + const schema = pathComponents.pop(); + const tableName = pathComponents.pop(); + + if (schema !== SCHEMA_PATH) { + throw new Error("Invalid resource URI"); + } + + return new Promise((resolve, reject) => { + pool.query( + "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", + [tableName], + (error: MySQLErrorType, results: ColumnRow[]) => { + if (error) reject(error); + resolve({ + contents: [ + { + uri: request.params.uri, + mimeType: "application/json", + text: JSON.stringify(results, null, 2), + }, + ], + }); + }, + ); + }); +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "mysql_query", + description: "Run a read-only MySQL query", + inputSchema: { + type: "object", + properties: { + sql: { type: "string" }, + }, + }, + }, + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "mysql_query") { + const sql = request.params.arguments?.sql as string; + + return new Promise((resolve, reject) => { + pool.getConnection((err: MySQLErrorType, connection: PoolConnection) => { + if (err) reject(err); + + // @INFO: Set session to read only BEFORE beginning the transaction + connection.query( + "SET SESSION TRANSACTION READ ONLY", + (err: MySQLErrorType) => { + if (err) { + connection.release(); + reject(err); + return; + } + + connection.beginTransaction((err: MySQLErrorType) => { + if (err) { + connection.release(); + reject(err); + return; + } + + connection.query( + sql, + (error: MySQLErrorType, results: QueryResult) => { + if (error) { + connection.rollback(() => { + connection.release(); + reject(error); + }); + return; + } + + // @INFO: Reset the transaction mode back to default before releasing + connection.rollback(() => { + connection.query( + "SET SESSION TRANSACTION READ WRITE", + (err: MySQLErrorType) => { + connection.release(); + if (err) { + console.warn( + "Failed to reset transaction mode:", + err, + ); + } + resolve({ + content: [ + { + type: "text", + text: JSON.stringify(results, null, 2), + }, + ], + isError: false, + }); + }, + ); + }); + }, + ); + }); + }, + ); + }); + }); + } + throw new Error(`Unknown tool: ${request.params.name}`); +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +process.on("SIGINT", () => { + pool.end((err: MySQLErrorType) => { + if (err) console.error("Error closing pool:", err); + process.exit(err ? 1 : 0); + }); +}); + +runServer().catch(console.error); diff --git a/src/mysql/package.json b/src/mysql/package.json new file mode 100644 index 00000000..d3c6dfe8 --- /dev/null +++ b/src/mysql/package.json @@ -0,0 +1,30 @@ +{ + "name": "@modelcontextprotocol/server-mysql", + "version": "0.6.2", + "description": "MCP server for interacting with MySQL databases", + "license": "MIT", + "author": "Ben Borla (https://github.com/benborla)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-mysql": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.0.1", + "mysql": "^2.18.1" + }, + "devDependencies": { + "@types/pg": "^8.11.10", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/mysql/pnpm-lock.yaml b/src/mysql/pnpm-lock.yaml new file mode 100644 index 00000000..e2e2fe59 --- /dev/null +++ b/src/mysql/pnpm-lock.yaml @@ -0,0 +1,440 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 1.0.1 + version: 1.0.1 + mysql: + specifier: ^2.18.1 + version: 2.18.1 + devDependencies: + '@types/pg': + specifier: ^8.11.10 + version: 8.11.10 + shx: + specifier: ^0.3.4 + version: 0.3.4 + typescript: + specifier: ^5.6.2 + version: 5.7.2 + +packages: + + '@modelcontextprotocol/sdk@1.0.1': + resolution: {integrity: sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==} + + '@types/node@22.10.1': + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bignumber.js@9.0.0: + resolution: {integrity: sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mysql@2.18.1: + resolution: {integrity: sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==} + engines: {node: '>= 0.6'} + + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + readable-stream@2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + + shx@0.3.4: + resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==} + engines: {node: '>=6'} + hasBin: true + + sqlstring@2.3.1: + resolution: {integrity: sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + +snapshots: + + '@modelcontextprotocol/sdk@1.0.1': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.0 + zod: 3.23.8 + + '@types/node@22.10.1': + dependencies: + undici-types: 6.20.0 + + '@types/pg@8.11.10': + dependencies: + '@types/node': 22.10.1 + pg-protocol: 1.7.0 + pg-types: 4.0.2 + + balanced-match@1.0.2: {} + + bignumber.js@9.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + bytes@3.1.2: {} + + concat-map@0.0.1: {} + + content-type@1.0.5: {} + + core-util-is@1.0.3: {} + + depd@2.0.0: {} + + fs.realpath@1.0.0: {} + + function-bind@1.1.2: {} + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + interpret@1.4.0: {} + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + isarray@1.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimist@1.2.8: {} + + mysql@2.18.1: + dependencies: + bignumber.js: 9.0.0 + readable-stream: 2.3.7 + safe-buffer: 5.1.2 + sqlstring: 2.3.1 + + obuf@1.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + path-is-absolute@1.0.1: {} + + path-parse@1.0.7: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-protocol@1.7.0: {} + + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + postgres-array@3.0.2: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@2.1.0: {} + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + + process-nextick-args@2.0.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + + readable-stream@2.3.7: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + rechoir@0.6.2: + dependencies: + resolve: 1.22.8 + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + safe-buffer@5.1.2: {} + + safer-buffer@2.1.2: {} + + setprototypeof@1.2.0: {} + + shelljs@0.8.5: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + + shx@0.3.4: + dependencies: + minimist: 1.2.8 + shelljs: 0.8.5 + + sqlstring@2.3.1: {} + + statuses@2.0.1: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + supports-preserve-symlinks-flag@1.0.0: {} + + toidentifier@1.0.1: {} + + typescript@5.7.2: {} + + undici-types@6.20.0: {} + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + wrappy@1.0.2: {} + + zod@3.23.8: {} diff --git a/src/mysql/tsconfig.json b/src/mysql/tsconfig.json new file mode 100644 index 00000000..ec5da158 --- /dev/null +++ b/src/mysql/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] +} From c5a2c0f44620159f74c64f9ddd1764cca79a4782 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 14:19:37 +0800 Subject: [PATCH 02/11] Removed pnpm locked json file --- src/mysql/pnpm-lock.yaml | 440 --------------------------------------- 1 file changed, 440 deletions(-) delete mode 100644 src/mysql/pnpm-lock.yaml diff --git a/src/mysql/pnpm-lock.yaml b/src/mysql/pnpm-lock.yaml deleted file mode 100644 index e2e2fe59..00000000 --- a/src/mysql/pnpm-lock.yaml +++ /dev/null @@ -1,440 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@modelcontextprotocol/sdk': - specifier: 1.0.1 - version: 1.0.1 - mysql: - specifier: ^2.18.1 - version: 2.18.1 - devDependencies: - '@types/pg': - specifier: ^8.11.10 - version: 8.11.10 - shx: - specifier: ^0.3.4 - version: 0.3.4 - typescript: - specifier: ^5.6.2 - version: 5.7.2 - -packages: - - '@modelcontextprotocol/sdk@1.0.1': - resolution: {integrity: sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==} - - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} - - '@types/pg@8.11.10': - resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - bignumber.js@9.0.0: - resolution: {integrity: sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} - - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} - engines: {node: '>= 0.4'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mysql@2.18.1: - resolution: {integrity: sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==} - engines: {node: '>= 0.6'} - - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-numeric@1.0.2: - resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} - engines: {node: '>=4'} - - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} - - pg-types@4.0.2: - resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} - engines: {node: '>=10'} - - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} - engines: {node: '>=12'} - - postgres-bytea@3.0.0: - resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} - engines: {node: '>= 6'} - - postgres-date@2.1.0: - resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} - engines: {node: '>=12'} - - postgres-interval@3.0.0: - resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} - engines: {node: '>=12'} - - postgres-range@1.1.4: - resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} - - readable-stream@2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} - - rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} - hasBin: true - - shx@0.3.4: - resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==} - engines: {node: '>=6'} - hasBin: true - - sqlstring@2.3.1: - resolution: {integrity: sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==} - engines: {node: '>= 0.6'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - -snapshots: - - '@modelcontextprotocol/sdk@1.0.1': - dependencies: - content-type: 1.0.5 - raw-body: 3.0.0 - zod: 3.23.8 - - '@types/node@22.10.1': - dependencies: - undici-types: 6.20.0 - - '@types/pg@8.11.10': - dependencies: - '@types/node': 22.10.1 - pg-protocol: 1.7.0 - pg-types: 4.0.2 - - balanced-match@1.0.2: {} - - bignumber.js@9.0.0: {} - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - bytes@3.1.2: {} - - concat-map@0.0.1: {} - - content-type@1.0.5: {} - - core-util-is@1.0.3: {} - - depd@2.0.0: {} - - fs.realpath@1.0.0: {} - - function-bind@1.1.2: {} - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - interpret@1.4.0: {} - - is-core-module@2.15.1: - dependencies: - hasown: 2.0.2 - - isarray@1.0.0: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimist@1.2.8: {} - - mysql@2.18.1: - dependencies: - bignumber.js: 9.0.0 - readable-stream: 2.3.7 - safe-buffer: 5.1.2 - sqlstring: 2.3.1 - - obuf@1.1.2: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - path-is-absolute@1.0.1: {} - - path-parse@1.0.7: {} - - pg-int8@1.0.1: {} - - pg-numeric@1.0.2: {} - - pg-protocol@1.7.0: {} - - pg-types@4.0.2: - dependencies: - pg-int8: 1.0.1 - pg-numeric: 1.0.2 - postgres-array: 3.0.2 - postgres-bytea: 3.0.0 - postgres-date: 2.1.0 - postgres-interval: 3.0.0 - postgres-range: 1.1.4 - - postgres-array@3.0.2: {} - - postgres-bytea@3.0.0: - dependencies: - obuf: 1.1.2 - - postgres-date@2.1.0: {} - - postgres-interval@3.0.0: {} - - postgres-range@1.1.4: {} - - process-nextick-args@2.0.1: {} - - raw-body@3.0.0: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - unpipe: 1.0.0 - - readable-stream@2.3.7: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - rechoir@0.6.2: - dependencies: - resolve: 1.22.8 - - resolve@1.22.8: - dependencies: - is-core-module: 2.15.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - safe-buffer@5.1.2: {} - - safer-buffer@2.1.2: {} - - setprototypeof@1.2.0: {} - - shelljs@0.8.5: - dependencies: - glob: 7.2.3 - interpret: 1.4.0 - rechoir: 0.6.2 - - shx@0.3.4: - dependencies: - minimist: 1.2.8 - shelljs: 0.8.5 - - sqlstring@2.3.1: {} - - statuses@2.0.1: {} - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - supports-preserve-symlinks-flag@1.0.0: {} - - toidentifier@1.0.1: {} - - typescript@5.7.2: {} - - undici-types@6.20.0: {} - - unpipe@1.0.0: {} - - util-deprecate@1.0.2: {} - - wrappy@1.0.2: {} - - zod@3.23.8: {} From ef0123d8891aa89738d7f28a8443e4991cc5f452 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 14:51:07 +0800 Subject: [PATCH 03/11] Corrected type and fixed build errors --- src/mysql/index.ts | 6 +++--- src/mysql/package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mysql/index.ts b/src/mysql/index.ts index 7d14aa47..e15f2dae 100644 --- a/src/mysql/index.ts +++ b/src/mysql/index.ts @@ -44,7 +44,7 @@ const MYSQL_DB = process.env.MYSQL_DB || ""; const pool = mysql.createPool({ connectionLimit: 10, host: MYSQL_HOST, - port: MYSQL_PORT, + port: Number(MYSQL_PORT), user: MYSQL_USER, password: MYSQL_PASS, database: MYSQL_DB, @@ -54,7 +54,7 @@ const SCHEMA_PATH = "schema"; server.setRequestHandler(ListResourcesRequestSchema, async () => { return new Promise((resolve, reject) => { - pool.query( + pool.query( "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", (error: MySQLErrorType, results: TableRow[]) => { if (error) reject(error); @@ -85,7 +85,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { } return new Promise((resolve, reject) => { - pool.query( + pool.query( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", [tableName], (error: MySQLErrorType, results: ColumnRow[]) => { diff --git a/src/mysql/package.json b/src/mysql/package.json index d3c6dfe8..f5c72bdc 100644 --- a/src/mysql/package.json +++ b/src/mysql/package.json @@ -23,7 +23,8 @@ "mysql": "^2.18.1" }, "devDependencies": { - "@types/pg": "^8.11.10", + "@types/node": "^20.10.0", + "@types/mysql": "^2.15.26", "shx": "^0.3.4", "typescript": "^5.6.2" } From 68549532d4dc14686920559251bf4b1712bd5de3 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 14:52:33 +0800 Subject: [PATCH 04/11] removed index.js, should rely on the build index.js in dist/index.js --- src/mysql/index.js | 184 --------------------------------------------- 1 file changed, 184 deletions(-) delete mode 100644 src/mysql/index.js diff --git a/src/mysql/index.js b/src/mysql/index.js deleted file mode 100644 index b2992e25..00000000 --- a/src/mysql/index.js +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env node - -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListResourcesRequestSchema, - ListToolsRequestSchema, - ReadResourceRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import mysql from "mysql"; - -const server = new Server( - { - name: "example-servers/mysql", - version: "0.1.0", - }, - { - capabilities: { - resources: {}, - tools: {}, - }, - }, -); - -const MYSQL_HOST = process.env.MYSQL_HOST || "127.0.0.1"; -const MYSQL_PORT = process.env.MYSQL_PORT || "3306"; -const MYSQL_USER = process.env.MYSQL_USER || "root"; -const MYSQL_PASS = process.env.MYSQL_PASS || ""; -const MYSQL_DB = process.env.MYSQL_DB || ""; - -const pool = mysql.createPool({ - connectionLimit: 10, - host: MYSQL_HOST, - port: MYSQL_PORT, - user: MYSQL_USER, - password: MYSQL_PASS, - database: MYSQL_DB, -}); - -const SCHEMA_PATH = "schema"; - -server.setRequestHandler(ListResourcesRequestSchema, async () => { - return new Promise((resolve, reject) => { - pool.query( - "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", - (error, results) => { - if (error) reject(error); - resolve({ - resources: results.map((row) => ({ - uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl) - .href, - mimeType: "application/json", - name: `"${row.table_name}" database schema`, - })), - }); - }, - ); - }); -}); - -server.setRequestHandler(ReadResourceRequestSchema, async (request) => { - const resourceUrl = new URL(request.params.uri); - - const pathComponents = resourceUrl.pathname.split("/"); - const schema = pathComponents.pop(); - const tableName = pathComponents.pop(); - - if (schema !== SCHEMA_PATH) { - throw new Error("Invalid resource URI"); - } - - return new Promise((resolve, reject) => { - pool.query( - "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", - [tableName], - (error, results) => { - if (error) reject(error); - resolve({ - contents: [ - { - uri: request.params.uri, - mimeType: "application/json", - text: JSON.stringify(results, null, 2), - }, - ], - }); - }, - ); - }); -}); - -server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: [ - { - name: "mysql_query", - description: "Run a read-only MySQL query", - inputSchema: { - type: "object", - properties: { - sql: { type: "string" }, - }, - }, - }, - ], - }; -}); - -server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name === "mysql_query") { - const sql = request.params.arguments?.sql; - - return new Promise((resolve, reject) => { - pool.getConnection((err, connection) => { - if (err) reject(err); - - // @INFO: Set session to read only BEFORE beginning the transaction - connection.query("SET SESSION TRANSACTION READ ONLY", (err) => { - if (err) { - connection.release(); - reject(err); - return; - } - - connection.beginTransaction((err) => { - if (err) { - connection.release(); - reject(err); - return; - } - - connection.query(sql, (error, results) => { - if (error) { - connection.rollback(() => { - connection.release(); - reject(error); - }); - return; - } - - connection.rollback(() => { - // @INFO: Reset the transaction mode back to default before releasing - connection.query( - "SET SESSION TRANSACTION READ WRITE", - (err) => { - connection.release(); - if (err) { - console.warn("Failed to reset transaction mode:", err); - } - resolve({ - content: [ - { - type: "text", - text: JSON.stringify(results, null, 2), - }, - ], - isError: false, - }); - }, - ); - }); - }); - }); - }); - }); - }); - } - throw new Error(`Unknown tool: ${request.params.name}`); -}); - -async function runServer() { - const transport = new StdioServerTransport(); - await server.connect(transport); -} - -process.on("SIGINT", () => { - pool.end((err) => { - if (err) console.error("Error closing pool:", err); - process.exit(err ? 1 : 0); - }); -}); - -runServer().catch(console.error); From 90fabce9d57c46fde3565aac6350fd4b4aa4d63c Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 15:11:37 +0800 Subject: [PATCH 05/11] refactored code for better readability and maintainability --- src/mysql/index.ts | 330 +++++++++++++++++++++++++-------------------- 1 file changed, 186 insertions(+), 144 deletions(-) diff --git a/src/mysql/index.ts b/src/mysql/index.ts index e15f2dae..82f0e4d9 100644 --- a/src/mysql/index.ts +++ b/src/mysql/index.ts @@ -8,7 +8,7 @@ import { ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; -import mysql, { MysqlError, PoolConnection, OkPacket } from "mysql"; +import mysql, { MysqlError, PoolConnection } from "mysql"; type MySQLErrorType = MysqlError | null; @@ -21,187 +21,229 @@ interface ColumnRow { data_type: string; } -type QueryResult = OkPacket | any[] | any; -const server = new Server( - { +const config = { + server: { name: "example-servers/mysql", version: "0.1.0", }, - { - capabilities: { - resources: {}, - tools: {}, - }, + mysql: { + host: process.env.MYSQL_HOST || "127.0.0.1", + port: Number(process.env.MYSQL_PORT || "3306"), + user: process.env.MYSQL_USER || "root", + password: process.env.MYSQL_PASS || "", + database: process.env.MYSQL_DB || "", + connectionLimit: 10, }, -); + paths: { + schema: "schema", + }, +}; -const MYSQL_HOST = process.env.MYSQL_HOST || "127.0.0.1"; -const MYSQL_PORT = process.env.MYSQL_PORT || "3306"; -const MYSQL_USER = process.env.MYSQL_USER || "root"; -const MYSQL_PASS = process.env.MYSQL_PASS || ""; -const MYSQL_DB = process.env.MYSQL_DB || ""; +const mysqlQuery = ( + connection: PoolConnection, + sql: string, + params: any[] = [], +): Promise => { + return new Promise((resolve, reject) => { + connection.query(sql, params, (error: MySQLErrorType, results: any) => { + if (error) reject(error); + else resolve(results); + }); + }); +}; -const pool = mysql.createPool({ - connectionLimit: 10, - host: MYSQL_HOST, - port: Number(MYSQL_PORT), - user: MYSQL_USER, - password: MYSQL_PASS, - database: MYSQL_DB, +const mysqlGetConnection = (pool: mysql.Pool): Promise => { + return new Promise( + ( + resolve: (value: PoolConnection | PromiseLike) => void, + reject, + ) => { + pool.getConnection( + (error: MySQLErrorType, connection: PoolConnection) => { + if (error) reject(error); + else resolve(connection); + }, + ); + }, + ); +}; + +const mysqlBeginTransaction = (connection: PoolConnection): Promise => { + return new Promise((resolve, reject) => { + connection.beginTransaction((error: MySQLErrorType) => { + if (error) reject(error); + else resolve(); + }); + }); +}; + +const mysqlRollback = (connection: PoolConnection): Promise => { + return new Promise((resolve, _) => { + connection.rollback(() => resolve()); + }); +}; + +const pool = mysql.createPool(config.mysql); +const server = new Server(config.server, { + capabilities: { + resources: {}, + tools: {}, + }, }); -const SCHEMA_PATH = "schema"; +async function executeQuery(sql: string, params: any[] = []): Promise { + const connection = await mysqlGetConnection(pool); + try { + const results = await mysqlQuery(connection, sql, params); + return results; + } finally { + connection.release(); + } +} +async function executeReadOnlyQuery(sql: string): Promise { + const connection = await mysqlGetConnection(pool); + + try { + // Set read-only mode + await mysqlQuery(connection, "SET SESSION TRANSACTION READ ONLY"); + + // Begin transaction + await mysqlBeginTransaction(connection); + + // Execute query + const results = await mysqlQuery(connection, sql); + + // Rollback transaction (since it's read-only) + await mysqlRollback(connection); + + // Reset to read-write mode + await mysqlQuery(connection, "SET SESSION TRANSACTION READ WRITE"); + + return { + content: [ + { + type: "text", + text: JSON.stringify(results, null, 2), + }, + ], + isError: false, + }; + } catch (error) { + await mysqlRollback(connection); + throw error; + } finally { + connection.release(); + } +} + +// Request handlers server.setRequestHandler(ListResourcesRequestSchema, async () => { - return new Promise((resolve, reject) => { - pool.query( - "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", - (error: MySQLErrorType, results: TableRow[]) => { - if (error) reject(error); - resolve({ - resources: results.map((row: TableRow) => ({ - uri: new URL( - `${row.table_name}/${SCHEMA_PATH}`, - `${MYSQL_HOST}:${MYSQL_PORT}`, - ).href, - mimeType: "application/json", - name: `"${row.table_name}" database schema`, - })), - }); - }, - ); - }); + const results = (await executeQuery( + "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", + )) as TableRow[]; + + return { + resources: results.map((row: TableRow) => ({ + uri: new URL( + `${row.table_name}/${config.paths.schema}`, + `${config.mysql.host}:${config.mysql.port}`, + ).href, + mimeType: "application/json", + name: `"${row.table_name}" database schema`, + })), + }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const resourceUrl = new URL(request.params.uri); - const pathComponents = resourceUrl.pathname.split("/"); const schema = pathComponents.pop(); const tableName = pathComponents.pop(); - if (schema !== SCHEMA_PATH) { + if (schema !== config.paths.schema) { throw new Error("Invalid resource URI"); } - return new Promise((resolve, reject) => { - pool.query( - "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", - [tableName], - (error: MySQLErrorType, results: ColumnRow[]) => { - if (error) reject(error); - resolve({ - contents: [ - { - uri: request.params.uri, - mimeType: "application/json", - text: JSON.stringify(results, null, 2), - }, - ], - }); - }, - ); - }); -}); + const results = (await executeQuery( + "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", + [tableName], + )) as ColumnRow[]; -server.setRequestHandler(ListToolsRequestSchema, async () => { return { - tools: [ + contents: [ { - name: "mysql_query", - description: "Run a read-only MySQL query", - inputSchema: { - type: "object", - properties: { - sql: { type: "string" }, - }, - }, + uri: request.params.uri, + mimeType: "application/json", + text: JSON.stringify(results, null, 2), }, ], }; }); +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "mysql_query", + description: "Run a read-only MySQL query", + inputSchema: { + type: "object", + properties: { + sql: { type: "string" }, + }, + }, + }, + ], +})); + server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name === "mysql_query") { - const sql = request.params.arguments?.sql as string; - - return new Promise((resolve, reject) => { - pool.getConnection((err: MySQLErrorType, connection: PoolConnection) => { - if (err) reject(err); - - // @INFO: Set session to read only BEFORE beginning the transaction - connection.query( - "SET SESSION TRANSACTION READ ONLY", - (err: MySQLErrorType) => { - if (err) { - connection.release(); - reject(err); - return; - } - - connection.beginTransaction((err: MySQLErrorType) => { - if (err) { - connection.release(); - reject(err); - return; - } - - connection.query( - sql, - (error: MySQLErrorType, results: QueryResult) => { - if (error) { - connection.rollback(() => { - connection.release(); - reject(error); - }); - return; - } - - // @INFO: Reset the transaction mode back to default before releasing - connection.rollback(() => { - connection.query( - "SET SESSION TRANSACTION READ WRITE", - (err: MySQLErrorType) => { - connection.release(); - if (err) { - console.warn( - "Failed to reset transaction mode:", - err, - ); - } - resolve({ - content: [ - { - type: "text", - text: JSON.stringify(results, null, 2), - }, - ], - isError: false, - }); - }, - ); - }); - }, - ); - }); - }, - ); - }); - }); + if (request.params.name !== "mysql_query") { + throw new Error(`Unknown tool: ${request.params.name}`); } - throw new Error(`Unknown tool: ${request.params.name}`); + + const sql = request.params.arguments?.sql as string; + return executeReadOnlyQuery(sql); }); +// Server startup and shutdown async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); } -process.on("SIGINT", () => { - pool.end((err: MySQLErrorType) => { - if (err) console.error("Error closing pool:", err); - process.exit(err ? 1 : 0); +const shutdown = async (signal: string) => { + console.log(`Received ${signal}. Shutting down...`); + return new Promise((resolve, reject) => { + pool.end((err: MySQLErrorType) => { + if (err) { + console.error("Error closing pool:", err); + reject(err); + } else { + resolve(); + } + }); }); +}; + +process.on("SIGINT", async () => { + try { + await shutdown("SIGINT"); + process.exit(0); + } catch (err) { + process.exit(1); + } }); -runServer().catch(console.error); +process.on("SIGTERM", async () => { + try { + await shutdown("SIGTERM"); + process.exit(0); + } catch (err) { + process.exit(1); + } +}); + +runServer().catch((error: unknown) => { + console.error("Server error:", error); + process.exit(1); +}); From bd0a6a9ef22d35e69baf791b1919d07adeb289dc Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 15:26:13 +0800 Subject: [PATCH 06/11] removed unused type --- src/mysql/index.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/mysql/index.ts b/src/mysql/index.ts index 82f0e4d9..d5c32851 100644 --- a/src/mysql/index.ts +++ b/src/mysql/index.ts @@ -21,6 +21,14 @@ interface ColumnRow { data_type: string; } +interface QueryResult { + content: Array<{ + type: string; + text: string; + }>; + isError: boolean; +} + const config = { server: { name: "example-servers/mysql", @@ -39,11 +47,11 @@ const config = { }, }; -const mysqlQuery = ( +const mysqlQuery = ( connection: PoolConnection, sql: string, params: any[] = [], -): Promise => { +): Promise => { return new Promise((resolve, reject) => { connection.query(sql, params, (error: MySQLErrorType, results: any) => { if (error) reject(error); @@ -91,17 +99,17 @@ const server = new Server(config.server, { }, }); -async function executeQuery(sql: string, params: any[] = []): Promise { +async function executeQuery(sql: string, params: any[] = []): Promise { const connection = await mysqlGetConnection(pool); try { - const results = await mysqlQuery(connection, sql, params); + const results = await mysqlQuery(connection, sql, params); return results; } finally { connection.release(); } } -async function executeReadOnlyQuery(sql: string): Promise { +async function executeReadOnlyQuery(sql: string): Promise { const connection = await mysqlGetConnection(pool); try { @@ -120,7 +128,7 @@ async function executeReadOnlyQuery(sql: string): Promise { // Reset to read-write mode await mysqlQuery(connection, "SET SESSION TRANSACTION READ WRITE"); - return { + return { content: [ { type: "text", From 386516cca1938a23717c13d4ef39cda3a135347a Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 15:26:20 +0800 Subject: [PATCH 07/11] removed unusd type --- src/mysql/index.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/mysql/index.ts b/src/mysql/index.ts index d5c32851..b6b5867b 100644 --- a/src/mysql/index.ts +++ b/src/mysql/index.ts @@ -21,14 +21,6 @@ interface ColumnRow { data_type: string; } -interface QueryResult { - content: Array<{ - type: string; - text: string; - }>; - isError: boolean; -} - const config = { server: { name: "example-servers/mysql", From c78b18f08a13a41fe96e5cf482d7de4361d067ed Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Mon, 9 Dec 2024 15:34:00 +0800 Subject: [PATCH 08/11] updated project readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45899a32..22eeb1c8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ These servers aim to demonstrate MCP features and the Typescript and Python SDK. - **[Google Maps](src/google-maps)** - Location services, directions, and place details - **[Memory](src/memory)** - Knowledge graph-based persistent memory system - **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection +- **[MySQL](src/mysql)** - Read-only database access with schema inspection for MySQL - **[Puppeteer](src/puppeteer)** - Browser automation and web scraping - **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io - **[Sequential Thinking](src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences From cc154c07ec52ee4160702483687a820b1f150f86 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Tue, 10 Dec 2024 02:48:42 +0800 Subject: [PATCH 09/11] Updated readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22eeb1c8..6530e0d9 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ These servers aim to demonstrate MCP features and the Typescript and Python SDK. - **[Google Maps](src/google-maps)** - Location services, directions, and place details - **[Memory](src/memory)** - Knowledge graph-based persistent memory system - **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection -- **[MySQL](src/mysql)** - Read-only database access with schema inspection for MySQL - **[Puppeteer](src/puppeteer)** - Browser automation and web scraping - **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io - **[Sequential Thinking](src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences @@ -54,7 +53,8 @@ A growing set of community-developed and maintained servers demonstrates various - **[MCP Installer](https://github.com/anaisbetts/mcp-installer)** - This server is a server that installs other MCP servers for you. - **[Spotify MCP](https://github.com/varunneal/spotify-mcp)** - This MCP allows an LLM to play and use Spotify. - **[Inoyu](https://github.com/sergehuber/inoyu-mcp-unomi-server)** - Interact with an Apache Unomi CDP customer data platform to retrieve and update customer profiles -- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** - MySQL database integration with configurable access controls and schema inspection +- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** - (by DesignComputer) MySQL database integration based on Python with configurable access controls and schema inspection +- **[MySQL](https://github.com/benborla/mcp-server-mysql)** - (by benborla) MySQL database integration based on NodeJS with configurable access controls and schema inspection - **[BigQuery](https://github.com/LucasHild/mcp-server-bigquery)** (by LucasHild) - This server enables LLMs to inspect database schemas and execute queries on BigQuery. - **[BigQuery](https://github.com/ergut/mcp-bigquery-server)** (by ergut) - Server implementation for Google BigQuery integration that enables direct BigQuery database access and querying capabilities - **[Todoist](https://github.com/abhiz123/todoist-mcp-server)** - Interact with Todoist to manage your tasks. From f6f8361f095be0215fb33021a704c3c9de527d59 Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Tue, 10 Dec 2024 02:53:58 +0800 Subject: [PATCH 10/11] removed unused files, this has been moved to its own repo --- package.json | 3 +- src/mysql/README.md | 53 --------- src/mysql/index.ts | 249 ---------------------------------------- src/mysql/package.json | 31 ----- src/mysql/tsconfig.json | 10 -- 5 files changed, 1 insertion(+), 345 deletions(-) delete mode 100644 src/mysql/README.md delete mode 100644 src/mysql/index.ts delete mode 100644 src/mysql/package.json delete mode 100644 src/mysql/tsconfig.json diff --git a/package.json b/package.json index 1e7077a3..9d5e5ee2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@modelcontextprotocol/server-memory": "*", "@modelcontextprotocol/server-filesystem": "*", "@modelcontextprotocol/server-everart": "*", - "@modelcontextprotocol/server-sequential-thinking": "*", - "@modelcontextprotocol/server-mysql": "*" + "@modelcontextprotocol/server-sequential-thinking": "*" } } diff --git a/src/mysql/README.md b/src/mysql/README.md deleted file mode 100644 index 56fd7d14..00000000 --- a/src/mysql/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# MySQL - -A Model Context Protocol server that provides read-only access to MySQL databases. This server enables LLMs to inspect database schemas and execute read-only queries. - -## Components - -### Tools - -- **query** - - Execute read-only SQL queries against the connected database - - Input: `sql` (string): The SQL query to execute - - All queries are executed within a READ ONLY transaction - -### Resources - -The server provides schema information for each table in the database: - -- **Table Schemas** - - JSON schema information for each table - - Includes column names and data types - - Automatically discovered from database metadata - -## Usage with Claude Desktop - -To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "mysql": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-mysql", - ], - "env": { - "MYSQL_HOST": "127.0.0.1", - "MYSQL_PORT": "33067", - "MYSQL_USER": "root", - "MYSQL_PASS": "", - "MYSQL_DB": "db_name" - } - - } - } -} -``` - -Replace `/db_name` with your database name or leave it blank to retrieve all databases. - -## License - -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/mysql/index.ts b/src/mysql/index.ts deleted file mode 100644 index b6b5867b..00000000 --- a/src/mysql/index.ts +++ /dev/null @@ -1,249 +0,0 @@ -#!/usr/bin/env node - -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListResourcesRequestSchema, - ListToolsRequestSchema, - ReadResourceRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import mysql, { MysqlError, PoolConnection } from "mysql"; - -type MySQLErrorType = MysqlError | null; - -interface TableRow { - table_name: string; -} - -interface ColumnRow { - column_name: string; - data_type: string; -} - -const config = { - server: { - name: "example-servers/mysql", - version: "0.1.0", - }, - mysql: { - host: process.env.MYSQL_HOST || "127.0.0.1", - port: Number(process.env.MYSQL_PORT || "3306"), - user: process.env.MYSQL_USER || "root", - password: process.env.MYSQL_PASS || "", - database: process.env.MYSQL_DB || "", - connectionLimit: 10, - }, - paths: { - schema: "schema", - }, -}; - -const mysqlQuery = ( - connection: PoolConnection, - sql: string, - params: any[] = [], -): Promise => { - return new Promise((resolve, reject) => { - connection.query(sql, params, (error: MySQLErrorType, results: any) => { - if (error) reject(error); - else resolve(results); - }); - }); -}; - -const mysqlGetConnection = (pool: mysql.Pool): Promise => { - return new Promise( - ( - resolve: (value: PoolConnection | PromiseLike) => void, - reject, - ) => { - pool.getConnection( - (error: MySQLErrorType, connection: PoolConnection) => { - if (error) reject(error); - else resolve(connection); - }, - ); - }, - ); -}; - -const mysqlBeginTransaction = (connection: PoolConnection): Promise => { - return new Promise((resolve, reject) => { - connection.beginTransaction((error: MySQLErrorType) => { - if (error) reject(error); - else resolve(); - }); - }); -}; - -const mysqlRollback = (connection: PoolConnection): Promise => { - return new Promise((resolve, _) => { - connection.rollback(() => resolve()); - }); -}; - -const pool = mysql.createPool(config.mysql); -const server = new Server(config.server, { - capabilities: { - resources: {}, - tools: {}, - }, -}); - -async function executeQuery(sql: string, params: any[] = []): Promise { - const connection = await mysqlGetConnection(pool); - try { - const results = await mysqlQuery(connection, sql, params); - return results; - } finally { - connection.release(); - } -} - -async function executeReadOnlyQuery(sql: string): Promise { - const connection = await mysqlGetConnection(pool); - - try { - // Set read-only mode - await mysqlQuery(connection, "SET SESSION TRANSACTION READ ONLY"); - - // Begin transaction - await mysqlBeginTransaction(connection); - - // Execute query - const results = await mysqlQuery(connection, sql); - - // Rollback transaction (since it's read-only) - await mysqlRollback(connection); - - // Reset to read-write mode - await mysqlQuery(connection, "SET SESSION TRANSACTION READ WRITE"); - - return { - content: [ - { - type: "text", - text: JSON.stringify(results, null, 2), - }, - ], - isError: false, - }; - } catch (error) { - await mysqlRollback(connection); - throw error; - } finally { - connection.release(); - } -} - -// Request handlers -server.setRequestHandler(ListResourcesRequestSchema, async () => { - const results = (await executeQuery( - "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()", - )) as TableRow[]; - - return { - resources: results.map((row: TableRow) => ({ - uri: new URL( - `${row.table_name}/${config.paths.schema}`, - `${config.mysql.host}:${config.mysql.port}`, - ).href, - mimeType: "application/json", - name: `"${row.table_name}" database schema`, - })), - }; -}); - -server.setRequestHandler(ReadResourceRequestSchema, async (request) => { - const resourceUrl = new URL(request.params.uri); - const pathComponents = resourceUrl.pathname.split("/"); - const schema = pathComponents.pop(); - const tableName = pathComponents.pop(); - - if (schema !== config.paths.schema) { - throw new Error("Invalid resource URI"); - } - - const results = (await executeQuery( - "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?", - [tableName], - )) as ColumnRow[]; - - return { - contents: [ - { - uri: request.params.uri, - mimeType: "application/json", - text: JSON.stringify(results, null, 2), - }, - ], - }; -}); - -server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [ - { - name: "mysql_query", - description: "Run a read-only MySQL query", - inputSchema: { - type: "object", - properties: { - sql: { type: "string" }, - }, - }, - }, - ], -})); - -server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name !== "mysql_query") { - throw new Error(`Unknown tool: ${request.params.name}`); - } - - const sql = request.params.arguments?.sql as string; - return executeReadOnlyQuery(sql); -}); - -// Server startup and shutdown -async function runServer() { - const transport = new StdioServerTransport(); - await server.connect(transport); -} - -const shutdown = async (signal: string) => { - console.log(`Received ${signal}. Shutting down...`); - return new Promise((resolve, reject) => { - pool.end((err: MySQLErrorType) => { - if (err) { - console.error("Error closing pool:", err); - reject(err); - } else { - resolve(); - } - }); - }); -}; - -process.on("SIGINT", async () => { - try { - await shutdown("SIGINT"); - process.exit(0); - } catch (err) { - process.exit(1); - } -}); - -process.on("SIGTERM", async () => { - try { - await shutdown("SIGTERM"); - process.exit(0); - } catch (err) { - process.exit(1); - } -}); - -runServer().catch((error: unknown) => { - console.error("Server error:", error); - process.exit(1); -}); diff --git a/src/mysql/package.json b/src/mysql/package.json deleted file mode 100644 index f5c72bdc..00000000 --- a/src/mysql/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@modelcontextprotocol/server-mysql", - "version": "0.6.2", - "description": "MCP server for interacting with MySQL databases", - "license": "MIT", - "author": "Ben Borla (https://github.com/benborla)", - "homepage": "https://modelcontextprotocol.io", - "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "type": "module", - "bin": { - "mcp-server-mysql": "dist/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsc && shx chmod +x dist/*.js", - "prepare": "npm run build", - "watch": "tsc --watch" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "1.0.1", - "mysql": "^2.18.1" - }, - "devDependencies": { - "@types/node": "^20.10.0", - "@types/mysql": "^2.15.26", - "shx": "^0.3.4", - "typescript": "^5.6.2" - } -} diff --git a/src/mysql/tsconfig.json b/src/mysql/tsconfig.json deleted file mode 100644 index ec5da158..00000000 --- a/src/mysql/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "." - }, - "include": [ - "./**/*.ts" - ] -} From 14e904958ab230a0fca29172349dceaace450035 Mon Sep 17 00:00:00 2001 From: Ian Borla Date: Wed, 11 Dec 2024 20:23:25 +0800 Subject: [PATCH 11/11] Update README.md Co-authored-by: Justin Spahr-Summers --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6cb67721..66e49582 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ A growing set of community-developed and maintained servers demonstrates various - **[Spotify](https://github.com/varunneal/spotify-mcp)** - This MCP allows an LLM to play and use Spotify. - **[Inoyu](https://github.com/sergehuber/inoyu-mcp-unomi-server)** - Interact with an Apache Unomi CDP customer data platform to retrieve and update customer profiles - **[Snowflake](https://github.com/datawiz168/mcp-snowflake-service)** - This MCP server enables LLMs to interact with Snowflake databases, allowing for secure and controlled data operations. -- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** - (by DesignComputer) MySQL database integration based on Python with configurable access controls and schema inspection -- **[MySQL](https://github.com/benborla/mcp-server-mysql)** - (by benborla) MySQL database integration based on NodeJS with configurable access controls and schema inspection +- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** (by DesignComputer) - MySQL database integration in Python with configurable access controls and schema inspection +- **[MySQL](https://github.com/benborla/mcp-server-mysql)** (by benborla) - MySQL database integration in NodeJS with configurable access controls and schema inspection - **[MSSQL](https://github.com/aekanun2020/mcp-server/)** - MSSQL database integration with configurable access controls and schema inspection - **[BigQuery](https://github.com/LucasHild/mcp-server-bigquery)** (by LucasHild) - This server enables LLMs to inspect database schemas and execute queries on BigQuery. - **[BigQuery](https://github.com/ergut/mcp-bigquery-server)** (by ergut) - Server implementation for Google BigQuery integration that enables direct BigQuery database access and querying capabilities