Databases·
intermediate
·14 min read·Apr 4, 2026·Updated Oct 10, 2026
By Rad Tome·Lead AI Systems Architect·How we verify this

MySQL MCP Server Guide: Setup and Queries

Connect MySQL databases to Model Context Protocol agents: Step-by-step configuration, read-only permissions, schema discovery, and query execution.

mysqldatabasesqldata-analysismcpread-onlyqueriesschema-discovery
Config Target: claude_desktop_config.json
{
  "mcpServers": {
    "mysql": {
      "command": "npx",
      "args": [
        "-y",
        "@benborla29/mcp-server-mysql"
      ],
      "env": {
        "MYSQL_HOST": "localhost",
        "MYSQL_USER": "root",
        "MYSQL_PASSWORD": "",
        "MYSQL_DATABASE": "mydb"
      }
    }
  }
}
Interactive Tool
1-Click Export

Generate & Test MySQL MCP Server Config

One-click export with environment variables & path locators for Claude Desktop, Cursor, Windsurf, and OpenAI Codex CLI.

Open in Generator

MySQL MCP Server Guide: Setup and Queries

Connecting relational databases directly to language model agents enables engineering teams to automate schema analysis, synthesize business intelligence metrics, and diagnose application database state directly from terminal and IDE workflows. Through the Model Context Protocol (MCP), agents operating inside Claude Desktop, Cursor, or the OpenAI Codex CLI can introspect MySQL table definitions, explain query plans, and execute analytical queries without requiring developers to manually export raw CSV dumps.

However, giving language model agents direct database connectivity introduces significant reliability and security challenges. Without strict read-only role scoping, query timeouts, and connection pool sizing, an agent may inadvertently lock tables, execute unindexed Cartesian joins, or hallucinate destructive mutation statements.

This guide provides an end-to-end operational manual for deploying a secure, high-performance MySQL MCP server, including least-privilege user creation, multi-client configuration, a production TypeScript server using mysql2, JSON-RPC message traces, and troubleshooting procedures.


1. System Architecture and Protocol Mechanics

The MySQL MCP server runs as an independent local or containerized process that translates structured JSON-RPC tool invocations from the AI host into parameterized SQL queries against MySQL or MariaDB instances.

diagram

Security and Execution Pipeline

Mutation Blocked

Valid SELECT

AI Agent: tools/call query

MySQL MCP Server Process

Statement Type: SELECT Only?

-32603 Error: Mutations Prohibited

mysql2 Connection Pool: Max 10 Connections

MySQL 8.0 Server Instance

Row Stream with LIMIT 200

Markdown Table Serializer

Protocol Mechanics and Safety Controls

  1. ▸Standard Input/Output Transport: For local desktop and CLI agents, the MCP server communicates over stdio. All diagnostic logging and database warnings must be directed to standard error (stderr) to prevent protocol corruption.
  2. ▸Read-Only Transaction Enforcement: The server validates statements at the application layer and executes queries through an unprivileged MySQL account restricted exclusively to SELECT operations.
  3. ▸Connection Pool Management: Unlike standard web applications where requests arrive concurrently from thousands of users, an MCP server handles sequential tool calls from a single agent. The server uses a compact connection pool (5–10 connections) with aggressive idle timeouts to prevent socket exhaustion.

2. Environment Prerequisites and Software Matrix

Before configuring the MySQL MCP server, verify that your environment satisfies the following requirements:

ComponentMinimum VersionDescription
Node.js>= 20.0.0Server runtime environment
MySQL / MariaDBMySQL >= 8.0 / MariaDB >= 10.6Target database instance
mysql2>= 3.11.0High-performance MySQL client supporting promises and authentication
@modelcontextprotocol/sdk>= 1.2.0Core Model Context Protocol framework

Ensure port 3306 is accessible between your host environment and the database cluster.


3. Creating a Hardened Read-Only Database User

Never supply an administrative account (such as root) to an MCP server. Create a dedicated user account restricted strictly to reading necessary schemas:

sql
-- 1. Create dedicated user with strong password authentication
CREATE USER 'mcp_reader'@'%' IDENTIFIED WITH caching_sha2_password BY 'Secure_Reader_Password_2026!';

-- 2. Grant SELECT privileges only on designated application databases
GRANT SELECT, SHOW VIEW ON application_db.* TO 'mcp_reader'@'%';

-- 3. Grant schema introspection rights
GRANT SELECT ON information_schema.tables TO 'mcp_reader'@'%';
GRANT SELECT ON information_schema.columns TO 'mcp_reader'@'%';

-- 4. Apply execution constraints to prevent resource starvation
ALTER USER 'mcp_reader'@'%' WITH MAX_QUERIES_PER_HOUR 5000 MAX_USER_CONNECTIONS 10;

FLUSH PRIVILEGES;

4. Multi-Host Configuration Snippets

Configure your MCP clients to launch the MySQL server over stdio. Provide connection credentials via isolated environment variables.

Claude Desktop Configuration (claude_desktop_config.json)

json
{
  "mcpServers": {
    "mysql": {
      "command": "node",
      "args": ["/opt/mcp-servers/mysql/dist/index.js"],
      "env": {
        "MYSQL_HOST": "127.0.0.1",
        "MYSQL_PORT": "3306",
        "MYSQL_USER": "mcp_reader",
        "MYSQL_PASSWORD": "Secure_Reader_Password_2026!",
        "MYSQL_DATABASE": "application_db"
      }
    }
  }
}

Cursor IDE Configuration (.cursor/mcp.json)

json
{
  "mcpServers": {
    "mysql": {
      "command": "node",
      "args": ["./dist/index.js"],
      "env": {
        "MYSQL_HOST": "127.0.0.1",
        "MYSQL_PORT": "3306",
        "MYSQL_USER": "mcp_reader",
        "MYSQL_PASSWORD": "Secure_Reader_Password_2026!",
        "MYSQL_DATABASE": "application_db"
      }
    }
  }
}

OpenAI Codex CLI Configuration (~/.codex/config.toml)

toml
[mcp_servers.mysql]
command = "node"
args = ["/opt/mcp-servers/mysql/dist/index.js"]

[mcp_servers.mysql.env]
MYSQL_HOST = "127.0.0.1"
MYSQL_PORT = "3306"
MYSQL_USER = "mcp_reader"
MYSQL_PASSWORD = "Secure_Reader_Password_2026!"
MYSQL_DATABASE = "application_db"

5. Production TypeScript MySQL MCP Server

The following server implementation uses mysql2/promise to provide schema introspection (list_tables, describe_table) and query execution (query) with token-efficient markdown output:

typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import mysql from 'mysql2/promise';

const pool = mysql.createPool({
  host: process.env.MYSQL_HOST || 'localhost',
  port: parseInt(process.env.MYSQL_PORT || '3306', 10),
  user: process.env.MYSQL_USER,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  waitForConnections: true,
  connectionLimit: 10,
  maxIdle: 5,
  idleTimeout: 30000,
  queueLimit: 0,
  charset: 'utf8mb4'
});

const server = new Server(
  { name: 'mysql-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'list_tables',
        description: 'List all table names available in the current database.',
        inputSchema: { type: 'object', properties: {} }
      },
      {
        name: 'describe_table',
        description: 'Retrieve column names, data types, nullability, and primary keys for a table.',
        inputSchema: {
          type: 'object',
          properties: {
            table_name: { type: 'string', description: 'Target table name' }
          },
          required: ['table_name']
        }
      },
      {
        name: 'query',
        description: 'Execute a read-only SQL SELECT query. Maximum 200 records returned.',
        inputSchema: {
          type: 'object',
          properties: {
            sql: { type: 'string', description: 'SQL SELECT query string' }
          },
          required: ['sql']
        }
      }
    ]
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === 'list_tables') {
      const [rows] = await pool.query('SHOW TABLES');
      return {
        content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }]
      };
    }

    if (name === 'describe_table') {
      const tableName = args?.table_name as string;
      const [rows] = await pool.query('DESCRIBE ??', [tableName]);
      return {
        content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }]
      };
    }

    if (name === 'query') {
      const sql = (args?.sql as string).trim();

      // Guardrail: Enforce SELECT only
      if (!sql.toLowerCase().startsWith('select') && !sql.toLowerCase().startsWith('show')) {
        return {
          isError: true,
          content: [{ type: 'text', text: 'Error: Only SELECT and SHOW statements are permitted.' }]
        };
      }

      const boundedSql = sql.toLowerCase().includes('limit') ? sql : `${sql} LIMIT 200`;
      const [rows]: [any[], any] = await pool.query(boundedSql);

      if (rows.length === 0) {
        return { content: [{ type: 'text', text: 'Query returned 0 rows.' }] };
      }

      const headers = Object.keys(rows[0]);
      const headerLine = `| ${headers.join(' | ')} |`;
      const dividerLine = `| ${headers.map(() => '---').join(' | ')} |`;
      const dataLines = rows
        .map(row => `| ${headers.map(h => String(row[h] ?? 'NULL')).join(' | ')} |`)
        .join('\n');

      return {
        content: [{ type: 'text', text: `${headerLine}\n${dividerLine}\n${dataLines}` }]
      };
    }

    throw new Error(`Unrecognized tool: ${name}`);
  } catch (err: any) {
    return {
      isError: true,
      content: [{ type: 'text', text: `Database Error: ${err.message}` }]
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch((err) => {
  process.stderr.write(`Fatal error: ${err.message}\n`);
  process.exit(1);
});

6. JSON-RPC 2.0 Wire Payloads

Inspecting the underlying protocol frames helps verify proper operation across client logs.

Tool Invocation Request (tools/call)

json
{
  "jsonrpc": "2.0",
  "id": "mysql-query-101",
  "method": "tools/call",
  "params": {
    "name": "describe_table",
    "arguments": {
      "table_name": "orders"
    }
  }
}

Result Payload

json
{
  "jsonrpc": "2.0",
  "id": "mysql-query-101",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[\n  {\"Field\": \"id\", \"Type\": \"bigint unsigned\", \"Null\": \"NO\", \"Key\": \"PRI\"},\n  {\"Field\": \"total_amount\", \"Type\": \"decimal(10,2)\", \"Null\": \"NO\", \"Key\": \"\"}\n]"
      }
    ]
  }
}

7. Common Errors and Production Troubleshooting

When operating the MySQL MCP server in production environments, monitor for the following failure modes:

Error: ER_ACCESS_DENIED_ERROR: Access denied for user

  • ▸Root Cause: Invalid credentials or host specification mismatch. MySQL evaluates user permissions by both username and host (e.g. 'mcp_reader'@'localhost' vs 'mcp_reader'@'%').
  • ▸Resolution: Verify that the host definition matches the connection source, and test connectivity directly using the terminal MySQL client: mysql -h 127.0.0.1 -u mcp_reader -p.

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol

  • ▸Root Cause: MySQL 8.0 uses caching_sha2_password by default, which legacy or unbundled client libraries may fail to negotiate over unencrypted connections.
  • ▸Resolution: Upgrade to mysql2 version >= 3.9.0 or configure your user account to negotiate modern authentication securely.

Character Encoding Corruption (Garbled Unicode Text)

  • ▸Root Cause: Database tables contain UTF-8 characters (emojis, multi-byte international text) while the client connection defaults to latin1.
  • ▸Resolution: Always set charset: 'utf8mb4' in connection pool settings.

8. External Citations and Security Guardrails

Connecting language model agents to relational databases requires continuous governance:

Technical Changelog

Weekly Protocol Diffs & Verified Drops

New server guides, benchmarks, and security audits. No spam. Unsubscribe anytime.

Zero marketing spam. 1-click unsubscribe anytime.

Ready to Deploy?

Build your full agent toolstack in the Visual Generator

Combine MySQL with databases, search APIs, and memory graphs in a single configuration file.

Customize in Generator
Developer Verification & Feedback
0 reports

Did this setup guide work with your AI host?

Help test this configuration across Claude, Cursor, and Codex CLI. Be the first to report:

MySQL MCP Server Guide: Setup and Queries FAQ

What is the MySQL MCP Server Guide: Setup and Queries?

Connect MySQL databases to Model Context Protocol agents: Step-by-step configuration, read-only permissions, schema discovery, and query execution.

How do I configure MySQL MCP Server Guide: Setup and Queries in Claude Desktop or Cursor?

You can copy the configuration JSON from our guide or launch the interactive MCP Codex Config Generator at https://mcp-codex.com/generator to export valid configs in 1 click.

Can I use MySQL MCP Server Guide: Setup and Queries with the OpenAI Codex CLI?

Yes, OpenAI Codex CLI supports Model Context Protocol. You can add it directly to ~/.codex/config.toml or pass arguments to codex mcp add.

RT

Written by Rad Tome

Lead AI Systems Architect & Maintainer, MCP Codex

@RadTome

Specializing in Model Context Protocol (MCP) integrations, autonomous AI agent orchestration, and distributed developer toolchains. Researches and benchmarks production MCP client-server architectures across OpenAI Codex, Claude, and Cursor.

Editorial Integrity: Package names are checked against the npm registry and repositories against the GitHub API. Where a package could not be verified, the guide says so instead of giving you a command to run.

Related Guides