A secure Model Context Protocol (MCP) server that exposes database access to LLM clients via FastMCP.
Supported database providers:
- SQLite
- PostgreSQL
- MySQL
- Microsoft SQL Server (MSSQL)
- Safe-by-default SQL validation middleware
- Read-only mode (
DB_READ_ONLY=true) enforced before execution - Single statement enforcement
- Forbidden keyword detection
- Automatic row limiting (
LIMIT/TOP) - Optional table allowlist (
DB_ALLOWED_TABLES) - MCP tools designed for schema exploration and safe querying
src/sql_mcp_server/
main.py
config.py
errors.py
middleware/sql_validator.py
db/
tools/
Copy .env.example to .env and update values.
DB_PROVIDER=sqlite
SQLITE_PATH=./database.db
DB_READ_ONLY=true
DB_MAX_ROWS=100DB_PROVIDER=postgres
DB_HOST=localhost
DB_PORT=5432
DB_USER=myuser
DB_PASSWORD=mypassword
DB_DATABASE=mydb
DB_READ_ONLY=true
DB_MAX_ROWS=100DB_PROVIDER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USER=myuser
DB_PASSWORD=mypassword
DB_DATABASE=mydb
DB_READ_ONLY=true
DB_MAX_ROWS=100DB_PROVIDER=mssql
DB_HOST=localhost
DB_PORT=1433
DB_USER=myuser
DB_PASSWORD=mypassword
DB_DATABASE=mydb
DB_READ_ONLY=true
DB_MAX_ROWS=100python -m venv .venv
.venv\\Scripts\\activate
pip install -e .sql-mcp-serverThe server runs over stdio (FastMCP default) and can be wired to MCP-compatible clients.
Windsurf can launch this MCP server over stdio. You can configure it in:
~/.codeium/windsurf/mcp_config.json
The examples below use the "module" entrypoint (Option 2):
command: your venv Python executableargs:["-m", "sql_mcp_server.main"]
DB_READ_ONLY(optional, default:true)DB_MAX_ROWS(optional, default:100)DB_QUERY_TIMEOUT(optional, default:10seconds)DB_STATEMENT_TIMEOUT_MS(optional, default:DB_QUERY_TIMEOUT * 1000; caps statement execution time)DB_ALLOWED_TABLES(optional, comma-separated allowlist)
Required env fields:
DB_PROVIDER=sqliteSQLITE_PATH
{
"mcpServers": {
"sql-sqlite": {
"command": "c:\\dev\\code\\mcp\\sql_mcp_server\\.venv\\Scripts\\python.exe",
"args": ["-m", "sql_mcp_server.main"],
"disabled": false,
"env": {
"DB_PROVIDER": "sqlite",
"SQLITE_PATH": "./database.db",
"DB_READ_ONLY": "true",
"DB_MAX_ROWS": "100",
"DB_QUERY_TIMEOUT": "10",
"DB_ALLOWED_TABLES": ""
}
}
}
}Required env fields:
DB_PROVIDER=postgresDB_HOSTDB_PORT(optional, default driver-side; recommended to set)DB_USERDB_PASSWORDDB_DATABASE
{
"mcpServers": {
"sql-postgres": {
"command": "c:\\dev\\code\\mcp\\sql_mcp_server\\.venv\\Scripts\\python.exe",
"args": ["-m", "sql_mcp_server.main"],
"disabled": false,
"env": {
"DB_PROVIDER": "postgres",
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_USER": "myuser",
"DB_PASSWORD": "mypassword",
"DB_DATABASE": "mydb",
"DB_READ_ONLY": "true",
"DB_MAX_ROWS": "100",
"DB_QUERY_TIMEOUT": "10",
"DB_ALLOWED_TABLES": ""
}
}
}
}Required env fields:
DB_PROVIDER=mysqlDB_HOSTDB_PORT(optional, default driver-side; recommended to set)DB_USERDB_PASSWORDDB_DATABASE
{
"mcpServers": {
"sql-mysql": {
"command": "c:\\dev\\code\\mcp\\sql_mcp_server\\.venv\\Scripts\\python.exe",
"args": ["-m", "sql_mcp_server.main"],
"disabled": false,
"env": {
"DB_PROVIDER": "mysql",
"DB_HOST": "localhost",
"DB_PORT": "3306",
"DB_USER": "myuser",
"DB_PASSWORD": "mypassword",
"DB_DATABASE": "mydb",
"DB_READ_ONLY": "true",
"DB_MAX_ROWS": "100",
"DB_QUERY_TIMEOUT": "10",
"DB_ALLOWED_TABLES": ""
}
}
}
}Required env fields:
DB_PROVIDER=mssqlDB_HOSTDB_USERDB_PASSWORDDB_DATABASE
Optional env fields:
DB_PORT(optional; default:1433)DB_MSSQL_ODBC_DRIVER(optional; if unset the server will try:ODBC Driver 18 for SQL Server, thenODBC Driver 17 for SQL Server, thenSQL Server)DB_MSSQL_TRUST_SERVER_CERTIFICATE(optional; default:false; set totruefor local/dev when using a self-signed certificate)
{
"mcpServers": {
"sql-mssql": {
"command": "c:\\dev\\code\\mcp\\sql_mcp_server\\.venv\\Scripts\\python.exe",
"args": ["-m", "sql_mcp_server.main"],
"disabled": false,
"env": {
"DB_PROVIDER": "mssql",
"DB_HOST": "localhost",
"DB_PORT": "1433",
"DB_USER": "myuser",
"DB_PASSWORD": "mypassword",
"DB_DATABASE": "mydb",
"DB_MSSQL_ODBC_DRIVER": "ODBC Driver 17 for SQL Server",
"DB_MSSQL_TRUST_SERVER_CERTIFICATE": "true",
"DB_READ_ONLY": "true",
"DB_MAX_ROWS": "100",
"DB_QUERY_TIMEOUT": "10",
"DB_ALLOWED_TABLES": ""
}
}
}
}list_tables: List accessible tablesdescribe_table(table: str): Describe columns of a tablerun_select(query: str): Execute a validated, safe SELECT query
- Always use a database user with the least privileges possible.
- Prefer DB-level read-only privileges in addition to middleware enforcement.
MIT