index.ts•7.06 kB
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import mysql from "mysql2/promise";
const DB_CONFIG = {
host: process.env.MYSQL_HOST || "localhost",
port: parseInt(process.env.MYSQL_PORT || "3306"),
database: process.env.MYSQL_DB || "mysql",
user: process.env.MYSQL_USER || "root",
password: process.env.MYSQL_PASSWORD || "",
};
// Hàm parse connection string thành config object
function parseConnectionString(connectionString: string): any {
const config: any = {};
const pairs = connectionString.split(";");
for (const pair of pairs) {
const [key, value] = pair.split("=");
if (key && value) {
if (key.toLowerCase() === "port") {
config[key.toLowerCase()] = parseInt(value);
} else {
switch (key.toLowerCase()) {
case "username":
case "uid":
config["user"] = value;
break;
case "server":
case "host":
case "datasource":
config["host"] = value;
break;
case "password":
case "pwd":
config["password"] = value;
break;
case "database":
case "db":
case "initial catalog":
config["database"] = value;
break;
case "pooling":
// Ignore pooling parameter as we always use pooling
break;
default:
config[key.toLowerCase()] = value;
}
}
}
}
return config;
}
class MySQLServer {
private server: Server;
private defaultPool: mysql.Pool;
constructor() {
this.server = new Server(
{
name: "mysql-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.defaultPool = mysql.createPool(DB_CONFIG);
this.setupToolHandlers();
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.defaultPool.end();
await this.server.close();
process.exit(0);
});
}
private async executeQueryWithConfig(
query: string,
params: any[] | undefined,
connectionString?: string
) {
let pool = this.defaultPool;
let temporaryPool = false;
if (connectionString) {
const config = parseConnectionString(connectionString);
// Tạo pool mới với config từ connection string
pool = mysql.createPool(config);
temporaryPool = true;
}
try {
const [rows] = await pool.query(query, params || []);
return rows;
} finally {
// Đóng pool tạm thời sau khi query xong
if (temporaryPool) {
await pool.end();
}
}
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "execute_query",
description: "Thực thi câu query SQL",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Câu lệnh SQL cần thực thi",
},
params: {
type: "array",
description: "Các tham số cho câu query (optional)",
items: {
type: "any",
},
},
connectionString: {
type: "string",
description: "Connection string tùy chỉnh (optional)",
},
},
required: ["query"],
},
},
{
name: "list_tables",
description: "Lấy danh sách các bảng trong database",
inputSchema: {
type: "object",
properties: {
database: {
type: "string",
description: "Tên database (mặc định lấy từ connection)",
},
connectionString: {
type: "string",
description: "Connection string tùy chỉnh (optional)",
},
},
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
switch (request.params.name) {
case "execute_query": {
const { query, params, connectionString } = request.params
.arguments as {
query: string;
params?: any[];
connectionString?: string;
};
const result = await this.executeQueryWithConfig(
query,
params,
connectionString
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "list_tables": {
const { database, connectionString } = request.params.arguments as {
database?: string;
connectionString?: string;
};
let dbName = database;
if (!dbName) {
// Nếu không có database được chỉ định, sử dụng database từ connection
if (connectionString) {
const config = parseConnectionString(connectionString);
dbName = config.database || DB_CONFIG.database;
} else {
dbName = DB_CONFIG.database;
}
}
const query = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
ORDER BY table_name
`;
const result = await this.executeQueryWithConfig(
query,
[dbName],
connectionString
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error) {
console.error(error);
return {
content: [
{
type: "text",
text: `Database error: ${(error as Error).message}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("MySQL MCP server running on stdio");
}
}
const server = new MySQLServer();
server.run().catch(console.error);