A PHP/Symfony Model Context Protocol (MCP) server for executing read-only SQL queries against multiple databases.
Supports: MySQL, MariaDB, PostgreSQL, SQLite, SQL Server
- Quick Start
- Configuration
- MCP Client Setup
- Available Tools
- PII Detection & Redaction
- Security
- Development
- License
The Docker image includes all database drivers. Just 3 steps:
1. Download the example configuration:
curl -L https://raw.githubusercontent.com/ineersa/mcp-sql-server/refs/heads/main/docker-compose.example.yaml -o docker-compose.yaml2. Create your databases.yaml with your database connections:
doctrine:
dbal:
connections:
mydb:
url: "mysql://user:password@127.0.0.1:3306/mydb"3. Configure your MCP client (see MCP Client Setup)
That's it! Your MCP client will spawn the server automatically.
Requires PHP 8.4+ with database extensions installed:
git clone https://github.com/ineersa/mcp-sql-server.git
cd mcp-sql-server
composer install --no-dev| Variable | Default | Description |
|---|---|---|
DATABASE_CONFIG_FILE |
required | Path to the database configuration YAML file |
APP_ENV |
prod |
Application environment |
APP_DEBUG |
false |
Enable debug mode |
LOG_LEVEL |
warning |
Log level: debug, info, warning, error |
APP_LOG_DIR |
/tmp/database-mcp/log |
Directory for log files |
Database connections are defined in a YAML file (path set via DATABASE_CONFIG_FILE).
Configuration follows Doctrine DBAL standards with DSN and environment variable support.
doctrine:
dbal:
connections:
# SQLite with explicit path
local:
driver: "pdo_sqlite"
path: "var/test.sqlite"
# MySQL with URL/DSN format and PII redaction enabled
products:
url: "mysql://user:password@127.0.0.1:3306/mydb?serverVersion=8.0&charset=utf8mb4"
# PostgreSQL with environment variable
users:
url: "%env(POSTGRES_DSN)%"
# SQL Server with explicit parameters and driver options
analytics:
driver: "pdo_sqlsrv"
host: "127.0.0.1"
port: 1433
dbname: "analytics"
user: "sa"
password: "MyPassword123"
serverVersion: "2019"
options:
TrustServerCertificate: "yes"| Database | Driver | URL Scheme |
|---|---|---|
| MySQL/MariaDB | pdo_mysql |
mysql://, mysql2:// |
| PostgreSQL | pdo_pgsql |
postgres://, pgsql:// |
| SQLite | pdo_sqlite |
sqlite:// |
| SQL Server | pdo_sqlsrv |
sqlsrv://, mssql:// |
Add this to your MCP client's configuration (e.g., mcp.json or Claude Desktop settings).
{
"database": {
"command": "docker",
"args": [
"compose",
"-f",
"/path/to/docker-compose.yaml",
"run",
"--rm",
"database-mcp"
]
}
}{
"mcp": {
"database": {
"type": "local",
"command": [
"docker",
"compose",
"-f",
"/path/to/docker-compose.yaml",
"run",
"--rm",
"database-mcp"
],
"enabled": true
}
}
}{
"database": {
"command": "/path/to/bin/database-mcp",
"args": [],
"env": {
"DATABASE_CONFIG_FILE": "/path/to/databases.yaml",
"LOG_LEVEL": "info",
"APP_LOG_DIR": "/tmp/database-mcp/log"
}
}
}Verify your configuration works:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
docker compose run --rm database-mcpYou should see a JSON response with server capabilities.
# View recent logs
docker compose run --rm logs
# View a specific log entry by line number
docker compose run --rm logs --id=42
# View more entries
docker compose run --rm logs --limit=100docker compose pullResponse format (all tools below):
- TOON text payload: Results are returned as TOON-encoded data in
content[].text
Executes read-only SQL queries against a specified database connection.
Before querying information_schema/system catalogs for metadata or object definitions, prefer the schema tool with detail: "full" and relevant include flags.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
connection |
string | Yes | Name of the database connection to use |
query |
string | Yes | SQL query to execute (semicolon-separated for multiple queries) |
Example:
{
"name": "query",
"arguments": {
"connection": "production",
"query": "SELECT id, name, email FROM users LIMIT 10"
}
}Inspects database schema metadata for a specified connection.
Use detail: "full" + includeRoutines: true + includeViews: true as the default for trigger/function/procedure/view definitions. Prefer this over raw catalog queries.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
connection |
string | Yes | Name of the database connection to inspect |
filter |
string | No | Optional object-name filter ("" for all) |
detail |
string | No | Detail level: summary (default), columns, or full |
matchMode |
string | No | Filter matching mode: contains (default), prefix, exact, or glob |
includeViews |
boolean | No | Include views in output |
includeRoutines |
boolean | No | Include routines/triggers in output |
matchMode: "exact" matches by logical object name across quote and schema variants. For example, these are treated equivalently for exact matching:
active_users"active_users"public.active_users"public"."active_users"`active_users`/[active_users]
When detail is full, view/trigger/routine definitions are included in the response.
Object coverage in full detail:
- Tables: columns, indexes, foreign keys, check constraints, triggers (with trigger definitions)
- Views: SQL definition (when
includeViewsistrue) - Routines: function/procedure definitions (when
includeRoutinesistrue)
For columns and full, use a narrow filter whenever possible. Large payloads are rejected with ToolUsageError.
If matchMode is exact, filter is non-empty, and no object matches, the response includes diagnostics with normalized names and near matches:
diagnostics.status(no_exact_match)diagnostics.normalized_filterdiagnostics.normalized_names_trieddiagnostics.top_near_matches
Example:
{
"name": "schema",
"arguments": {
"connection": "production",
"filter": "users",
"detail": "columns",
"matchMode": "contains",
"includeViews": false,
"includeRoutines": false
}
}This server includes built-in PII (Personally Identifiable Information) detection and redaction using GLiNER models in ONNX format via the native gliner-rs-php extension.
The server supports any GLiNER model in ONNX format. While any compatible model can be used, we recommend and provide a helper for our tested GLiNER PII ONNX model (~1.8GB).
Using Docker (Recommended):
docker compose run --rm download-modelsUsing PHP:
php bin/console download-modelsThis downloads model.onnx and tokenizer.json to your local ./models directory.
PII redaction is configured per-connection in your databases.yaml. When enabled, query results are automatically scanned and sensitive data is replaced with [REDACTED_type] markers (e.g., [REDACTED_email], [REDACTED_ssn]).
doctrine:
dbal:
connections:
production:
url: "mysql://..."
pii_enabled: true # Redact PII in query results
development:
url: "mysql://..."
# pii_enabled defaults to falseYou can fine-tune the detection threshold and entity types in your databases.yaml:
pii:
threshold: 0.6 # Confidence threshold (0.0-1.0)
# Note: Default is 0.6. For higher recall (especially on dates), you can lower this to e.g. 0.2.
# Optional: Custom model paths (defaults to models/ in project root)
# tokenizer_path: "models/tokenizer.json"
# model_path: "models/model.onnx"
# Optional: Limit detection to specific entity types for better performance
labels:
- email
- phone_number
- credit_debit_card
- ssnView all 64 supported PII labels
labels:
# Personal
# - first_name
# - last_name
# - name
# - date_of_birth
# - age
# - gender
# - sexuality
# - race_ethnicity
# - religious_belief
# - political_view
# - occupation
# - employment_status
# - education_level
# Contact
- email
- phone_number
# - street_address
# - city
# - county
# - state
# - country
# - coordinate
# - zip_code
# - po_box
# Financial
- credit_debit_card
# - cvv
# - bank_routing_number
# - account_number
# - iban
# - swift_bic
# - pin
- ssn
# - tax_id
# - ein
# Government
# - passport_number
# - driver_license
# - license_plate
# - national_id
# - voter_id
# Digital/Technical
# - ipv4
# - ipv6
# - mac_address
# - url
# - user_name
# - password
# - device_identifier
# - imei
# - serial_number
# - api_key
# - secret_key
# Healthcare/PHI
# - medical_record_number
# - health_plan_beneficiary_number
# - blood_type
# - biometric_identifier
# - health_condition
# - medication
# - insurance_policy_number
# Temporal
# - date
# - time
# - date_time
# Organization
# - company_name
# - employee_id
# - customer_id
# - certificate_license_number
# - vehicle_identifierManual Extension Installation (non-Docker):
Install the gliner-rs-php extension:
curl -fsSL -o gliner.tar.gz \
https://github.com/ineersa/gliner-rs-php/releases/download/0.0.6/gliner-rs-php-0.0.6-linux-x86_64.tar.gz
tar -xzf gliner.tar.gz
cp libgliner_rs_php.so /usr/local/lib/php/extensions/
echo "extension=/usr/local/lib/php/extensions/libgliner_rs_php.so" > /usr/local/etc/php/conf.d/gliner.iniThis server enforces read-only mode through multiple layers:
- SQL Keyword Validation — Blocks forbidden keywords (
INSERT,UPDATE,DELETE,DROP,CREATE,ALTER,TRUNCATE, etc.) before execution - Platform SET Commands — Database-level read-only enforcement:
- MySQL/MariaDB:
SET SESSION transaction_read_only = 1 - PostgreSQL:
SET default_transaction_read_only = on - SQLite:
PRAGMA query_only = ON - SQL Server:
ApplicationIntent=ReadOnly
- MySQL/MariaDB:
- Sandboxed Execution — All queries run in a transaction that is always rolled back
Best Practice: Always use a database user with read-only permissions for additional security.
ApplicationIntent=ReadOnly only works for Always On Availability Groups and Azure SQL Database. For standalone instances, configure a read-only database user.
git clone https://github.com/ineersa/mcp-sql-server.git
cd mcp-sql-server
composer installnpx @modelcontextprotocol/inspector ./bin/database-mcpcomposer cs-fix # Fix code style
composer phpstan # Static analysis
composer tests # Run testscomposer docker-build # Build the image
composer docker-rebuild # Rebuild without cachesrc/
├── Command/ # Symfony console commands
├── Enum/ # PII entity types and groups
├── ReadOnly/ # DBAL middleware for read-only enforcement
├── Service/ # Core services (including PIIAnalyzerService)
└── Tools/ # MCP tool implementations
stubs/ # PHP extension stubs for IDE support
tests/ # PHPUnit test suites
config/ # Symfony configuration
MIT License — see LICENSE for details.