-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Internal documentation for mcp-mysql-read-only. If you are here to use the server, the README is the place to start. These pages are for people changing the code.
Every page explains not just what a class does but why it is shaped that way, because most of the non-obvious decisions here exist to close a specific failure that showed up in testing.
| Page | Covers |
|---|---|
| Architecture | Layers, request flow, the one piece of mutable state |
| Design Patterns | Which patterns are used, where, and what each one bought |
| Domain and Configuration | Value objects, the profile factory, the config loader, the registry |
| Database Layer | Pool manager, session initializer, query executor |
| Read Only Enforcement | The skeletonizer, the rule chain, identifier validation |
| Tools | The tool class hierarchy and all twelve tools |
| Server Lifecycle | Composition root, startup, shutdown, the stdin EOF trap |
| Testing | Suite layout, running without a local MySQL |
| Release Process | CI, Docker Hub publishing, wiki publishing |
The server speaks MCP over stdio. ApplicationFactory is the composition root: it reads configuration, builds every collaborator and hands a finished McpMySqlServer back. A ConnectionRegistry holds the profiles and the single active connection, and a ConnectionManager is the only thing allowed to change it. ConnectionPoolManager caches one mysql2 pool per distinct connection, so switching databases selects a different pool rather than reconnecting. ReadOnlyQueryValidator walks a chain of rules to decide whether a statement may run at all. Each tool is its own class deriving from BaseTool.
index.ts -> ApplicationFactory -> McpMySqlServer
|
builds and injects everything below
|
tools/ -> connections/ + database/ + validation/ + formatting/
|
domain/ (value objects)
Dependencies point inward. domain/ imports nothing of ours; validation/ and formatting/ import only types; tools/ never constructs a collaborator.
Everything is injected, nothing is a singleton. No class reads process.env or constructs its own dependencies, so every one can be unit tested with a fake. The earlier version kept the registry and pool cache in module-level state, which tests could only reach by re-importing modules with a cache-busting query string.
Failures are tool errors, never crashes. A bad query, a dead host, an unknown database: all return isError: true with a readable message. BaseTool enforces this so no handler can forget it.
Never throw at import time. A misconfigured environment produces a running server that explains the problem, not one that dies during handshake and looks like a broken install.
Validate before interpolating. Anything reaching a backtick-quoted identifier is checked against a strict allowlist first. Values go through placeholders wherever the query shape permits.
Assume the validator will eventually be wrong. The MySQL session is configured read-only independently, so a hole in the parser is not automatically a write.
Design
Internals
Process
Links