Skip to main content
Glama
Heelc

vmysql-mcp

by Heelc

MySQL Exec

mysql_exec
Destructive

Execute a single write-capable SQL statement on a MySQL database environment with policy-based security controls and environment routing.

Instructions

Run one write-capable SQL statement on a configured MySQL environment if policy allows.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
envYesEnvironment alias such as dev or stg.
databaseNoOptional database override allowed by server policy.
sqlYesOne SQL statement allowed by server policy.
timeoutMsNoRequested execution timeout in milliseconds.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
okYes
envYes
databaseNo
insertIdNo
warningsNo
elapsedMsYes
affectedRowsYes
statementTypeYes

Implementation Reference

  • The main handler function for mysql_exec tool. It resolves the execution context using policy, executes the SQL via the pool registry, and builds the success/error result with affected rows, insert ID, warnings, and elapsed time.
    async (args) => {
      try {
        const context = resolveExecContext(registry, args);
        const startedAt = Date.now();
        const result = await pools.exec(
          context.env,
          context.normalizedSql,
          context.database,
          context.timeoutMs,
        );
        const structuredContent: ExecStructuredContent = {
          ok: true,
          env: context.env,
          database: context.database,
          statementType: context.statementType,
          affectedRows: result.affectedRows,
          insertId: result.insertId,
          warnings: result.warnings,
          elapsedMs: Date.now() - startedAt,
        };
    
        return buildSuccessResult(
          `Affected ${structuredContent.affectedRows} row(s) in ${context.env}${context.database ? `.${context.database}` : ""} in ${structuredContent.elapsedMs}ms.`,
          structuredContent,
        );
      } catch (error) {
        return buildErrorResult(error, args.env);
      }
    },
  • Input schema for mysql_exec tool using Zod. Defines validation for env (environment alias), database (optional override), sql (the statement to execute), and timeoutMs (optional execution timeout).
    export const mysqlExecInputSchema = z.object({
      env: z.string().min(1).describe("Environment alias such as dev or stg."),
      database: z
        .string()
        .min(1)
        .optional()
        .describe("Optional database override allowed by server policy."),
      sql: z.string().min(1).describe("One SQL statement allowed by server policy."),
      timeoutMs: z
        .number()
        .int()
        .min(100)
        .optional()
        .describe("Requested execution timeout in milliseconds."),
    });
  • Registration function for mysql_exec tool. Registers the tool with MCP server including title, description, annotations (destructiveHint: true, readOnlyHint: false), input/output schemas, and the handler function.
    export const registerMysqlExecTool = (
      server: McpServer,
      registry: EnvironmentRegistry,
      pools: MysqlPoolRegistry,
    ) => {
      server.registerTool(
        "mysql_exec",
        {
          title: "MySQL Exec",
          description: "Run one write-capable SQL statement on a configured MySQL environment if policy allows.",
          annotations: {
            title: "MySQL Exec",
            readOnlyHint: false,
            destructiveHint: true,
            idempotentHint: false,
            openWorldHint: false,
          },
          inputSchema: mysqlExecInputSchema,
          outputSchema: buildMysqlExecOutputSchema(),
        },
        async (args) => {
          try {
            const context = resolveExecContext(registry, args);
            const startedAt = Date.now();
            const result = await pools.exec(
              context.env,
              context.normalizedSql,
              context.database,
              context.timeoutMs,
            );
            const structuredContent: ExecStructuredContent = {
              ok: true,
              env: context.env,
              database: context.database,
              statementType: context.statementType,
              affectedRows: result.affectedRows,
              insertId: result.insertId,
              warnings: result.warnings,
              elapsedMs: Date.now() - startedAt,
            };
    
            return buildSuccessResult(
              `Affected ${structuredContent.affectedRows} row(s) in ${context.env}${context.database ? `.${context.database}` : ""} in ${structuredContent.elapsedMs}ms.`,
              structuredContent,
            );
          } catch (error) {
            return buildErrorResult(error, args.env);
          }
        },
      );
    };
  • resolveExecContext helper function that validates SQL statements for mysql_exec. Checks for single statement, disallows WITH clauses, verifies write permissions, validates statement types (insert/update/delete/replace/ddl), checks DDL permissions, and resolves timeout settings.
    export const resolveExecContext = (
      registry: EnvironmentRegistry,
      args: ExecArgs,
    ): ResolvedExecutionContext & {
      normalizedSql: string;
      statementType: "insert" | "update" | "delete" | "replace" | "ddl";
    } => {
      const environment = registry.getEnvironment(args.env);
      const inspection = inspectSql(args.sql);
    
      if (!inspection.normalizedSql) {
        throw createError("SQL_EMPTY", "SQL cannot be empty.", false);
      }
      if (inspection.hasMultipleStatements) {
        throw createError(
          "SQL_MULTI_STATEMENT",
          "Only one SQL statement is allowed per call.",
          false,
        );
      }
    
      if (inspection.hasLeadingWithClause) {
        throw createError(
          "SQL_NOT_ALLOWED",
          "Top-level WITH statements are not allowed in mysql_exec.",
          false,
        );
      }
    
      if (environment.readOnly || !environment.allowWrite) {
        throw createError(
          "WRITE_DISABLED",
          `Write statements are not allowed for environment '${args.env}'.`,
          false,
        );
      }
    
      if (!isWriteType(inspection.statementType) && inspection.statementType !== "ddl") {
        throw createError(
          "SQL_NOT_ALLOWED",
          `Statement type '${inspection.mainKeyword || inspection.firstKeyword || "unknown"}' is not allowed in mysql_exec.`,
          false,
        );
      }
    
      if (inspection.statementType === "ddl" && !environment.allowDDL) {
        throw createError(
          "DDL_DISABLED",
          `DDL statements are not allowed for environment '${args.env}'.`,
          false,
        );
      }
    
      const database = args.database ?? environment.defaultDatabase;
      assertAllowedDatabase(environment, database);
      assertReferencedDatabasesAllowed(environment, inspection.normalizedSql);
    
      const serverConfig = registry.getServerConfig();
      return {
        env: args.env,
        database,
        environment,
        timeoutMs: resolveTimeout(
          args.timeoutMs,
          environment.defaultTimeoutMs,
          serverConfig.hardTimeoutMs,
          serverConfig.defaultTimeoutMs,
        ),
        normalizedSql: inspection.normalizedSql,
        statementType: inspection.statementType,
      };
    };
  • The exec method in MysqlPoolRegistry that performs the actual MySQL execution. Gets a connection from the pool, optionally changes to the specified database, executes the SQL with timeout, and returns affectedRows, insertId, and warnings.
    public async exec(
      env: string,
      sql: string,
      database: string | undefined,
      timeoutMs: number,
    ): Promise<ExecExecutionResult> {
      const pool = this.getPool(env);
      const connection = await pool.getConnection();
      try {
        if (database) {
          await connection.changeUser({ database });
        }
        const [result] = await connection.query<ResultSetHeader>({
          sql,
          timeout: timeoutMs,
        });
        return {
          affectedRows: result.affectedRows,
          insertId: result.insertId === 0 ? undefined : result.insertId,
          warnings: result.warningStatus,
        };
      } catch (error) {
        throw this.mapMysqlError(error);
      } finally {
        connection.release();
      }
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds valuable context beyond annotations: it specifies 'one SQL statement' (limiting batch operations), 'if policy allows' (implying authorization checks), and 'write-capable' (clarifying operation type). Annotations already indicate destructiveHint=true and non-idempotent, but the description reinforces this with 'write-capable' and policy constraints, without contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that front-loads the core action ('Run one write-capable SQL statement') and efficiently adds constraints ('on a configured MySQL environment if policy allows'). Every word contributes essential information without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (destructive write operation with policy constraints), the description is complete: it clarifies purpose, usage context, and behavioral traits. With annotations covering safety aspects (destructiveHint=true) and an output schema existing, the description effectively supplements without needing to detail return values or repeat structured data.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so parameters are fully documented in the schema. The description does not add specific meaning to individual parameters beyond implying 'env' and 'sql' are required (matching schema) and hinting at policy constraints for 'database' and 'sql'. Baseline score of 3 is appropriate as schema carries the burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Run one write-capable SQL statement'), the resource ('on a configured MySQL environment'), and distinguishes from its sibling mysql_query by specifying 'write-capable' versus presumably read-only querying. It provides precise scope with 'one SQL statement' and policy constraints.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states 'if policy allows' and specifies 'write-capable SQL statement,' which implies usage for write operations versus read queries handled by mysql_query. However, it does not explicitly name the sibling tool as an alternative or detail when not to use this tool beyond policy constraints.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Heelc/vmysql-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server