Skip to content

update some packages and make it work again #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
55 changes: 55 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# MCP Configuration
MCP_TRANSPORT=stdio

# Basic MySQL connection settings
MYSQL_HOST=localhost
# MYSQL_SOCKET_PATH=/tmp/mysql.sock
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASS=password
MYSQL_DB=

# Leave MYSQL_DB empty for multi-DB mode
# Set MYSQL_DB to a specific database name for single-DB mode

# Global write operation permissions (default to false for safety)
ALLOW_INSERT_OPERATION=false
ALLOW_UPDATE_OPERATION=false
ALLOW_DELETE_OPERATION=false
ALLOW_DDL_OPERATION=false

# Schema-specific permissions
# Format: "schema1:true,schema2:false"
SCHEMA_INSERT_PERMISSIONS=test_db:true,staging_db:false
SCHEMA_UPDATE_PERMISSIONS=test_db:true,staging_db:false
SCHEMA_DELETE_PERMISSIONS=test_db:false,staging_db:false
SCHEMA_DDL_PERMISSIONS=test_db:true,staging_db:false

# Multi-DB mode settings
# Set to true ONLY if you want to allow write operations in multi-DB mode without
# schema-specific permissions (not recommended)
MULTI_DB_WRITE_MODE=false

# SSL configuration
MYSQL_SSL=false
MYSQL_SSL_REJECT_UNAUTHORIZED=true

# Performance settings
MYSQL_POOL_SIZE=10
MYSQL_QUERY_TIMEOUT=30000
MYSQL_CACHE_TTL=60000

# Security settings
MYSQL_RATE_LIMIT=100
MYSQL_MAX_QUERY_COMPLEXITY=1000

# Monitoring settings
ENABLE_LOGGING=false
MYSQL_LOG_LEVEL=info
MYSQL_METRICS_ENABLED=false

# GCP
GCLOUD_PROJECT_ID="project-id-placeholder"
ARTIFACT_REGISTRY_REPOSITORY="artifacts-repository-placeholder"
ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME="mcp_mysql"
ARTIFACT_REGISTRY_REGION="region-placeholder"
15 changes: 12 additions & 3 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# MCP Configuration
MCP_TRANSPORT=stdio

# Basic MySQL connection settings
MYSQL_HOST=127.0.0.1
MYSQL_SOCKET_PATH=/tmp/mysql.sock
MYSQL_HOST=localhost
# MYSQL_SOCKET_PATH=/tmp/mysql.sock
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASS=your_password
MYSQL_PASS=password
MYSQL_DB=

# Leave MYSQL_DB empty for multi-DB mode
Expand Down Expand Up @@ -44,3 +47,9 @@ MYSQL_MAX_QUERY_COMPLEXITY=1000
ENABLE_LOGGING=false
MYSQL_LOG_LEVEL=info
MYSQL_METRICS_ENABLED=false

# GCP
GCLOUD_PROJECT_ID="project-id-placeholder"
ARTIFACT_REGISTRY_REPOSITORY="artifacts-repository-placeholder"
ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME="mcp_mysql"
ARTIFACT_REGISTRY_REGION="region-placeholder"
13 changes: 11 additions & 2 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# MCP Configuration
MCP_TRANSPORT=stdio

# Basic MySQL connection settings
MYSQL_HOST=127.0.0.1
MYSQL_HOST=localhost
# MYSQL_SOCKET_PATH=/tmp/mysql.sock
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASS=root
MYSQL_PASS=password
MYSQL_DB=

# Leave MYSQL_DB empty for multi-DB mode
Expand Down Expand Up @@ -48,3 +51,9 @@ MYSQL_METRICS_ENABLED=false
# Test settings
TEST_TIMEOUT=10000
TEST_POOL_SIZE=5

# GCP
GCLOUD_PROJECT_ID="project-id-placeholder"
ARTIFACT_REGISTRY_REPOSITORY="artifacts-repository-placeholder"
ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME="mcp_mysql"
ARTIFACT_REGISTRY_REGION="region-placeholder"
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ WORKDIR /app
COPY package.json pnpm-lock.yaml* /app/

# Install the dependencies
RUN pnpm install --frozen-lockfile --ignore-scripts
# RUN pnpm install --frozen-lockfile --ignore-scripts
RUN pnpm install --ignore-scripts

# Copy the rest of the application code
COPY . /app
COPY . .

# Build the application
RUN pnpm run build
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,27 @@ If you'd like to contribute to any of these areas, please check the issues on Gi
## License

This MCP server is licensed under the MIT License. See the LICENSE file for details.


## Guide

Env:
# MCP Configuration
MCP_TRANSPORT=stdio|http
# MYSQL_SOCKET_PATH=/tmp/mysql.sock
MYSQL_HOST=
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASS=
MYSQL_DB=

Run:
$ npm i
$ npm run dev
$ npm run build
$ npm run start
### debug
$ npx @modelcontextprotocol/inspector

### Cloud build
$ node --env-file=".env" docker_deploy_gcp_cloud_build.js
17 changes: 17 additions & 0 deletions docker_deploy_gcp_cloud_build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { execSync } from 'child_process';

const {
GCLOUD_PROJECT_ID,
ARTIFACT_REGISTRY_REPOSITORY,
ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME,
} = process.env;

const image = `${ARTIFACT_REGISTRY_REPOSITORY}/${ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME}_cloud_build`;

const buildCmd = `gcloud builds submit --project=${GCLOUD_PROJECT_ID} --pack image=${image}`;
console.log(buildCmd);
let output = execSync(buildCmd, { stdio: 'inherit' });
console.log(output);

console.log("Done");

29 changes: 29 additions & 0 deletions docker_deploy_gcp_local_build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { execSync } from 'child_process';

const {
GCLOUD_PROJECT_ID,
ARTIFACT_REGISTRY_REPOSITORY,
ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME,
ARTIFACT_REGISTRY_REGION
} = process.env;

if (!GCLOUD_PROJECT_ID || !ARTIFACT_REGISTRY_REPOSITORY || !ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME || !ARTIFACT_REGISTRY_REGION) {
console.error("Missing required environment variables.");
process.exit(1);
}

const image = `${ARTIFACT_REGISTRY_REPOSITORY}/${ARTIFACT_REGISTRY_DOCKER_IMAGE_NAME}`;
try {
// Build Docker image
console.log(`Building Docker image: ${image}`);
execSync(`docker build -t ${image} .`, { stdio: 'inherit' });

// Push Docker image
console.log(`Pushing Docker image: ${image}`);
execSync(`docker push ${image}`, { stdio: 'inherit' });

console.log("Image uploaded to GCP Artifact Registry successfully.");
} catch (err) {
console.error("Deployment failed:", err);
process.exit(1);
}
34 changes: 34 additions & 0 deletions examples/claude_desktop_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"C:\\Users\\<MY_USER_NAME>\\Desktop",
"C:\\Users\\<MY_USER_NAME>\\Downloads"
]
},
"mcp_server_mysql_remote": {
"command": "npx",
"args": ["mcp-remote", "<REMOTE_URL>/mcp"]
},
"mcp_server_mysql": {
"command": "c:\\Program Files\\nodejs\\node",
"args": [
"C:\\Users\\<MY_USER_NAME>\\OneDrive - Startup Nation Central\\Documents\\mcp-server-mysql\\dist\\index.js"
],
"env": {
"MYSQL_HOST": "localhost",
"MYSQL_PORT": "3306",
"MYSQL_USER": "root",
"MYSQL_PASS": "password",
"MYSQL_DB": "",
"ALLOW_INSERT_OPERATION": "false",
"ALLOW_UPDATE_OPERATION": "false",
"ALLOW_DELETE_OPERATION": "false",
"MCP_TRANSPORT": "stdio"
}
}
}
}
89 changes: 86 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { randomUUID } from "node:crypto";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
Expand Down Expand Up @@ -316,9 +320,88 @@ async function runServer(): Promise<void> {
log("info", "Database connection test successful");
connection.release();

const server = await getServer();
const transport = new StdioServerTransport();
await server.connect(transport);
// Choose transport based on env var MCP_TRANSPORT: "stdio" or "http" (default: http)
const TRANSPORT = process.env.MCP_TRANSPORT || "http";

if (TRANSPORT === "stdio") {
log("info", "Starting MCP server in stdio mode");
const server = await getServer();
const transport = new StdioServerTransport();
await server.connect(transport);
} else {
log("info", "Starting MCP server in HTTP mode with session management");
const app = express();
app.use(express.json());

// Store transports by session ID
const transports: { [sessionId: string]: { transport: StreamableHTTPServerTransport } } = {};

app.get("/", (req, res) => {

});
// POST /mcp: handle client-to-server communication
app.post(["/mcp","/sse"], async (req, res) => {

const sessionId = req.headers['mcp-session-id'] as string | undefined;

let transport: StreamableHTTPServerTransport;

if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId].transport;
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSid: string) => {
transports[newSid] = { transport };
}
});
// Clean up transport when closed (reference: README)
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
const server = await getServer();
await server.connect(transport);
} else {
// Invalid request
log("error", "Invalid MCP session request", { sessionId, body: req.body });
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided',
},
id: null,
});
return;
}

// Handle the request
await transport.handleRequest(req, res, req.body);
});

// GET and DELETE /mcp: handle server-to-client notifications and session termination
const handleSessionRequest = async (req: any, res: any) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId].transport;
await transport.handleRequest(req, res);
};

app.get('/mcp', handleSessionRequest);
app.delete('/mcp', handleSessionRequest);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
log("info", `MCP Streamable HTTP Server (multi-client) listening on port ${PORT}`);
});
}
} catch (error) {
log("error", "Fatal error during server startup:", error);
safeExit(1);
Expand Down
Loading