How easy is it to use the VillageSQL MCP extension?

How easy is it to use the VillageSQL MCP extension?

VillageSQL published Access MySQL over MCP with vsql-mcp . The extension serves the Model Context Protocol from inside the database, so an agent talks to MySQL over Streamable HTTP without a sidecar holding a credential.

The post already includes a fully functional prompt. Open the dropdown, copy it into your AI coding tool, and it walks through install, build, a least-privilege setup, a real question against demo data, and then tries to break the guardrails. I ran that prompt as written against a throwaway server.

So the answer to my question is: It’s very easy.

What follows is the output of that run via GrokBot. This was run some weeks ago, if only I had a bot that integrated with my Cursor workspace that could write this blog post in my Hugo automatically and publish to my website, rather than this manual work! (We can always work towards it)

The following content is created by Generative AI an may contain errors or hallucinations.

vsql-mcp demo on a throwaway VillageSQL server: results

  • Run: Thursday 24 September 2026, 08:35 to 08:39 AEST, torn down right after
  • Validated against: https://villagesql.com/blog/mcp/
  • Isolation: throwaway server on port 3308 under /workspace/vsql-mcp-demo. The existing servers on 3306 (/home/box/.villagesql, PID 113981) and 3307 (RAG demo, PID 87229) were not touched.

Step 1: Install and confirm the server

The curl | bash installer hit the GitHub API rate limit (60 requests per hour without authentication). The same release was installed by hand instead:

  • Release 0.0.6, asset villagesql-dev-server-mysql-8.4_0.0.6-linux-x86_64.tar.gz (plus the SDK)
  • mysqld --initialize-insecure, then started with --port=3308 --socket=... --vsql_allow_preview_extensions=ON --daemonize
  • HOME was set to /workspace/vsql-mcp-demo so the install lived only under the demo directory
mysql> SELECT VERSION();
VERSION()
8.4.11-villagesql-0.0.6
veb_dir                        = /workspace/vsql-mcp-demo/.villagesql/prebuilt/lib/veb/
vsql_allow_preview_extensions  = ON
Port PID Status
3306 113981 unchanged
3307 87229 unchanged
3308 548303 throwaway demo server

Step 2: Build the extension

  • The system rustc 1.85.1 was left alone. rustup was installed under the demo directory.
  • Rust 1.87.0 failed to build mysql_common 0.37.3 because it needs let-chains, so the build used rustc 1.98.1 (stable).
  • cargo install cargo-vsql installed version 0.0.6.
  • git clone https://github.com/villagesql/vsql-mcp, then cargo vsql package
  • This produced dist/vsql_mcp.veb (6,253,568 bytes), which was copied into veb_dir.

Step 3: Demo schema and INSTALL EXTENSION

The shop schema from the blog post was used exactly: customers (6 rows) and orders (13 rows, with a foreign key to customers).

SELECT VERSION();              -> 8.4.11-villagesql-0.0.6
SELECT COUNT(*) FROM customers -> 6
SELECT COUNT(*) FROM orders    -> 13
SET PERSIST vsql_allow_preview_extensions = ON;
INSTALL EXTENSION vsql_mcp;    -> OK

SHOW GLOBAL VARIABLES LIKE 'vsql_mcp%' (defaults right after install):

Variable Value
vsql_mcp.allow_write OFF
vsql_mcp.allowed_tables (empty)
vsql_mcp.bearer_token (empty)
vsql_mcp.db_url (empty)
vsql_mcp.max_rows 1000
vsql_mcp.port 3100
vsql_mcp.query_timeout 30
vsql_mcp.require_auth OFF
vsql_mcp.schema (empty)
vsql_mcp.session_ttl 1800
vsql_mcp.ssl_cert (empty)
vsql_mcp.ssl_key (empty)
vsql_mcp.ssl_port 3143
vsql_mcp.vsql_mcp_enabled OFF

Step 4: Least-privilege configuration

CREATE USER 'mcp'@'127.0.0.1' IDENTIFIED BY '<random, redacted>';
GRANT SELECT ON shop.* TO 'mcp'@'127.0.0.1';
SET GLOBAL vsql_mcp.db_url = 'mysql://mcp:<redacted>@127.0.0.1:3308';
SET GLOBAL vsql_mcp.schema = 'shop';
SET GLOBAL vsql_mcp.require_auth = ON;
SET GLOBAL vsql_mcp.bearer_token = '<random 64-hex token, redacted>';
SET GLOBAL vsql_mcp.port = 3400;
SET GLOBAL vsql_mcp.vsql_mcp_enabled = ON;
mysql> SELECT vsql_mcp.info();
{"enabled":true,"http_port":3400,"https_port":0,"port":3400,"protocol_version":"2025-06-18","schema":"shop","sessions_active":0,"ssl_port":3143}

The listener was bound to 127.0.0.1:3400 only.

Step 5: Answer a question using only the MCP tools

Endpoint: http://127.0.0.1:3400/mcp, with header Authorization: Bearer <token>.

initialize response:

{"id":1,"jsonrpc":"2.0","result":{"capabilities":{"resources":{},"tools":{}},"protocolVersion":"2025-06-18","serverInfo":{"name":"vsql_mcp","version":"0.0.6"}}}

tools/list returned 5 tools: list_schemas, list_tables, describe_table, query, explain. The write tool was absent because allow_write is OFF.

Question: Which customer has the highest total value of delivered orders?

Tools called, in order:

  1. list_schemas returned {"schemas":["shop"]}
  2. list_tables returned customers (6) and orders (13)
  3. describe_table on customers
  4. describe_table on orders
  5. query with the delivered-totals join
{
  "columns": ["name", "country", "delivered_total"],
  "row_count": 3,
  "rows": [
    {"name": "Carla Reyes", "country": "MX", "delivered_total": "420.00"},
    {"name": "Ben Osei", "country": "GH", "delivered_total": "310.45"},
    {"name": "Grace Liu", "country": "SG", "delivered_total": "276.25"}
  ],
  "truncated": false
}

Answer: Carla Reyes, with $420.00 in delivered orders. This matches the blog post.

SHOW STATUS LIKE 'vsql_mcp%' after step 5:

Variable Value
vsql_mcp.http_port 3400
vsql_mcp.https_port 0
vsql_mcp.rows_returned_total 3
vsql_mcp.sessions_active 1
vsql_mcp.tool_calls_total 5
vsql_mcp.tool_errors_total 0

Step 6: Try to break it

# Attempt Result
1 tools/list with no bearer token HTTP 401 Unauthorized, empty body
2 query with UPDATE shop.orders ... only a single read-only statement is allowed by the query tool
3 query with SELECT ... FROM mysql.user schema 'mysql' is outside the exposed schema; only 'shop' is available, and table names must be qualified with it
4a SET GLOBAL vsql_mcp.allowed_tables = 'orders', then list_tables Only orders is listed; customers is omitted
4b describe_table on customers table 'customers' is not in vsql_mcp.allowed_tables
4c query that joins customers table 'customers' is not in vsql_mcp.allowed_tables
Extra Request with Origin: https://evil.example HTTP 403 Forbidden

SHOW STATUS after the break attempts: tool_calls_total 10, tool_errors_total 4, rows_returned_total 3, sessions_active 1.

Summary

Step What ran What came back
1 mysql-8.4 prebuilt 0.0.6 on port 3308 8.4.11-villagesql-0.0.6; 3306 and 3307 unchanged
2 rustup, cargo install cargo-vsql, cargo vsql package vsql_mcp.veb built (needed rustc 1.98.1)
3 shop schema and INSTALL EXTENSION vsql_mcp 6 customers, 13 orders; 14 vsql_mcp.* settings appeared
4 SELECT-only mcp user and SET GLOBAL settings info() shows it listening on 3400 for schema shop
5 MCP initialize, tools/list, and 5 tool calls Carla Reyes, $420.00
6 Four break attempts plus an Origin check Refusal messages matching the blog, plus 401 and 403
Teardown Uninstall, drop user and schema, stop server, delete files Only 3306 and 3307 remain, with their original PIDs

What did not behave as asked

  1. The curl | bash installer was rate-limited by GitHub, so the same 0.0.6 mysql-8.4 prebuilt was downloaded by hand.
  2. Rust 1.87 was not enough in practice. A dependency (mysql_common 0.37.3) needs let-chains, so the build used rustc 1.98.1.
  3. The first vsql_mcp.info() call right after enabling showed enabled:false and http_port:0, even though the port was already listening. A second call showed the expected values.
  4. The endpoint was not registered as an account-level MCP connector, because a remote connector can’t reach 127.0.0.1 on this machine. The MCP tools were called directly over Streamable HTTP at the same endpoint the blog registers.
  5. The prompt asked for “a few dozen rows”. The blog’s exact shop data (6 plus 13 rows) was used so the results could be checked against the post.

Teardown

SET GLOBAL vsql_mcp.vsql_mcp_enabled = OFF;
-- bearer_token, db_url, schema, allowed_tables reset; require_auth OFF; port 3100
UNINSTALL EXTENSION vsql_mcp;
DROP USER IF EXISTS 'mcp'@'127.0.0.1';
DROP DATABASE IF EXISTS shop;
SHOW DATABASES -> information_schema, mysql, performance_schema, sys, villagesql

After that, the 3308 server was shut down and /workspace/vsql-mcp-demo was deleted. The final port check showed only 3306 (PID 113981) and 3307 (PID 87229) listening.

Tagged with: MySQL VillageSQL Extensions

Related Posts

Getting VECTOR capabilities in MySQL 8.4 using VillageSQL

For this quick verification of the 0.0.7 development branch of VillageSQL with the new vsql-vector plugin. Recreate the VillageSQL Percona Live presentation using MySQL version 8.4 and SVECTOR. Demonstrate a more detailed example using SVECTOR(1024) string embedded data.

Read more

Why INSERT IGNORE should not be used

Let’s say you’re building a reference table from source data, e.g. a silver medallion table from a primary source. In this example I am using a file of random locations on the globe extracted from OpenStreetMap (OSM) as my primary source.

Read more

Curated MySQL Data Sets for Realistic Testing

Synthetic benchmarks have their place, but I have always preferred working with real data. Not client production data — that stays private — but publicly available datasets that reflect the messy shapes, skewed distributions, and indexing challenges you encounter in the wild.

Read more