Skip to content

Latest commit

 

History

132 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Database MCP Server

A PHP/Symfony Model Context Protocol (MCP) server for executing read-only SQL queries against multiple databases.

Supports: MySQL, MariaDB, PostgreSQL, SQLite, SQL Server


Table of Contents


Quick Start

Using Docker (Recommended)

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.yaml

2. 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.

Without Docker

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

Configuration

Environment Variables

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 Configuration File

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.

Example Configuration

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"

Supported Databases

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://

MCP Client Setup

Add this to your MCP client's configuration (e.g., mcp.json or Claude Desktop settings).

Docker Compose launch configuration

Use this invocation for MCP over stdio on macOS or Linux. Replace /path/to/docker-compose.yaml with your Compose file's absolute path.

Use docker if it is available on your MCP client's PATH. Otherwise, run command -v docker in your terminal and use the returned absolute path instead. GUI clients might not inherit your terminal's PATH.

The example passes environment variables through /usr/bin/env, so it does not require your MCP client to support an env block. Place the database entry under your client's server configuration key, such as mcpServers in Claude Desktop.

{
    "database": {
        "command": "/usr/bin/env",
        "args": [
            "COMPOSE_MENU=false",
            "COMPOSE_PROGRESS=plain",
            "COMPOSE_ANSI=never",
            "COMPOSE_IGNORE_ORPHANS=true",
            "DOCKER_CLI_HINTS=false",
            "docker",
            "compose",
            "-f",
            "/path/to/docker-compose.yaml",
            "run",
            "--rm",
            "--no-deps",
            "-T",
            "--quiet-pull",
            "database-mcp"
        ]
    }
}

MCP uses stdout for JSON-RPC messages. A pseudo-TTY or interactive CLI output can interfere with that transport. These settings disable TTY allocation and reduce Compose's interactive output:

Setting Effect
COMPOSE_MENU=false Disables the Compose interactive menu.
COMPOSE_PROGRESS=plain Uses plain progress output instead of an interactive display.
COMPOSE_ANSI=never Disables ANSI formatting in Compose output.
COMPOSE_IGNORE_ORPHANS=true Suppresses warnings about orphan containers.
DOCKER_CLI_HINTS=false Disables Docker CLI hints.
-T Disables pseudo-TTY allocation.
--quiet-pull Suppresses image pull progress.
--no-deps Prevents Compose from starting dependency services.

If your Compose file declares database dependencies, start them separately before you connect the MCP client. Do not redirect stderr to stdout for this invocation.

OpenCode

Use the same environment settings and Compose options in OpenCode's command array. Replace the Compose file path and, if needed, the Docker command as described above.

{
    "mcp": {
        "database": {
            "type": "local",
            "command": [
                "/usr/bin/env",
                "COMPOSE_MENU=false",
                "COMPOSE_PROGRESS=plain",
                "COMPOSE_ANSI=never",
                "COMPOSE_IGNORE_ORPHANS=true",
                "DOCKER_CLI_HINTS=false",
                "docker",
                "compose",
                "-f",
                "/path/to/docker-compose.yaml",
                "run",
                "--rm",
                "--no-deps",
                "-T",
                "--quiet-pull",
                "database-mcp"
            ],
            "enabled": true
        }
    }
}

Local Installation

{
    "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"
        }
    }
}

Testing Your Setup

Verify your configuration works:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
env COMPOSE_MENU=false COMPOSE_PROGRESS=plain COMPOSE_ANSI=never \
    COMPOSE_IGNORE_ORPHANS=true DOCKER_CLI_HINTS=false \
    docker compose run --rm --no-deps -T --quiet-pull database-mcp

You should see a JSON response with server capabilities.

Viewing Logs

# 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=100

Updating

docker compose pull

Available Tools

Response format (all tools below):

  • TOON text payload: Results are returned as TOON-encoded data in content[].text

query

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"
    }
}

schema

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 includeViews is true)
  • Routines: function/procedure definitions (when includeRoutines is true)

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_filter
  • diagnostics.normalized_names_tried
  • diagnostics.top_near_matches

Example:

{
    "name": "schema",
    "arguments": {
        "connection": "production",
        "filter": "users",
        "detail": "columns",
        "matchMode": "contains",
        "includeViews": false,
        "includeRoutines": false
    }
}

PII Detection & Redaction

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.

1. Set up models

Docker images include the GLiNER PII ONNX model and tokenizer at /app/models. PII detection needs no model download or Hugging Face access at runtime. The bundled files add about 1.8 GB before image compression.

For an existing Docker setup, follow the upgrade guide to remove the old model mount and download service. The old mount hides the bundled files, even if the host directory is empty.

To use a custom compatible GLiNER ONNX model, mount its directory and set pii.tokenizer_path and pii.model_path to the container paths.

The image build downloads a pinned model revision and verifies SHA-256 checksums. Building the image requires Hugging Face access. The model is distributed under the NVIDIA Open Model License, with the agreement and attribution in /app/models/LICENSE.html and /app/models/Notice.txt.

To verify bundled model inference without network access after building an image:

docker run --rm --network none -i --entrypoint php ineersa/database-mcp:latest < tests/docker-model-smoke.php

For a native PHP installation, download the files before enabling PII detection:

php bin/console download-models

This downloads model.onnx and tokenizer.json to your local ./models directory.

2. Enable on Specific Connections

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 false

3. Optionally Customize PII Settings

You 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
        - ssn
View 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_identifier

Manual 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.ini

Security

Read-Only Enforcement

This server enforces read-only mode through multiple layers:

  1. SQL Keyword Validation — Blocks forbidden keywords (INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, etc.) before execution
  2. 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
  3. 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.

SQL Server Note

ApplicationIntent=ReadOnly only works for Always On Availability Groups and Azure SQL Database. For standalone instances, configure a read-only database user.


Development

Setup

git clone https://github.com/ineersa/mcp-sql-server.git
cd mcp-sql-server
composer install

Testing with MCP Inspector

npx @modelcontextprotocol/inspector ./bin/database-mcp

Code Quality

composer cs-fix    # Fix code style
composer phpstan   # Static analysis
composer tests     # Run tests

Building Docker Image

composer docker-build     # Build the image
composer docker-rebuild   # Rebuild without cache

Project Structure

src/
├── 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

License

MIT License — see LICENSE for details.

About

SQL MCP server

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages