Skip to content

tmssoftware/Database-MCP-Server

Repository files navigation

FireDAC MCP Server

Turn any database FireDAC can reach into an MCP server that an AI assistant can query and (optionally) modify — driven by a single connection string and a chosen access mode.

You give the server one FireDAC connection string plus a mode (readonly / readwrite / full). It opens the connection, owns it for its whole lifetime, and mediates every call from the AI client — enforcing the access mode and a SQL-safety classifier on the way in, and marshalling result sets to JSON on the way out.

Because it is driven entirely by the connection string and by FireDAC's engine-agnostic metadata API, the same binary works with:

Engine Example DriverID
SQLite SQLite
PostgreSQL PG
MySQL / MariaDB MySQL
SQL Server MSSQL
Oracle Ora
Firebird / InterBase FB / IB
Anything with an ODBC driver ODBC

The physical driver-link units are linked in by FireDACMCPServer.dpr; add or remove them there to control which engines a build supports.


Files

File Purpose
FireDACMCPServer.dpr Entry point: command-line parsing, driver links, server wiring.
DBConnectionManager.pas The engine-agnostic core: connection handling, SQL classifier, access-mode enforcement, parameter binding, result marshalling, tool registration.
FireDACMCPServer.dproj Delphi project (Win32 console).

Building

Open FireDACMCPServer.dproj in the IDE and build (Shift+F9), or from a command prompt:

"C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat"
msbuild FireDACMCPServer.dproj /t:Build /p:Config=Debug /p:Platform=Win32

The executable lands in Win32\Debug\FireDACMCPServer.exe.


Usage

FireDACMCPServer [options]
Option Description
-conn="<connection string>" The FireDAC connection string. Required unless -conn-env is used.
-conn-env=<ENV_VAR> Read the connection string from an environment variable instead (keeps passwords off the command line and out of client config files).
-mode=<readonly|readwrite|full> Access level. Default: readonly.
-maxrows=<n> Hard cap on rows returned by run_query (default 1000).
-timeout=<ms> Per-command execution timeout in milliseconds.
-allow-multi Permit several ;-separated statements in a single call (off by default).
-help Show help and exit.

Access modes

Mode What the AI can do Tools exposed
readonly (default) SELECT and metadata only. No mutation tool is registered at all — the AI has no way to change anything. db_info, list_tables, describe_table, run_query
readwrite The above + INSERT / UPDATE / DELETE / MERGE (DML). above + run_statement
full The above + DDL (CREATE / ALTER / DROP / TRUNCATE) and otherwise-blocked statements (PRAGMA, ATTACH, …). above (run_statement also accepts DDL)

Examples

SQLite, read-only (safe default):

FireDACMCPServer -conn="DriverID=SQLite;Database=C:\data\app.db"

PostgreSQL, read/write, password from an environment variable:

set PG_CONN=DriverID=PG;Server=localhost;Database=sales;User_Name=app;Password=secret
FireDACMCPServer -conn-env=PG_CONN -mode=readwrite

SQL Server, read-only, Windows auth:

FireDACMCPServer -conn="DriverID=MSSQL;Server=localhost;Database=Northwind;OSAuthent=Yes;ODBCAdvanced=TrustServerCertificate=yes"

SQL Server + ODBC Driver 18 note: Driver 18 encrypts by default and validates the server certificate, so against a local instance with a self-signed cert you will see "SSL Provider: The certificate chain was issued by an authority that is not trusted." Pass the certificate through with ODBCAdvanced=TrustServerCertificate=yes (keeps encryption on) or disable encryption with Encrypt=No. FireDAC's own TrustServerCertificate parameter is not forwarded to the ODBC driver — use ODBCAdvanced.


Registering with an MCP client (stdio)

Add an entry to your client config (claude_desktop_config.json, .mcp.json, …):

{
  "mcpServers": {
    "database": {
      "command": "C:\\path\\to\\FireDACMCPServer.exe",
      "args": [
        "-conn-env=APP_DB_CONN",
        "-mode=readonly"
      ],
      "env": {
        "APP_DB_CONN": "DriverID=SQLite;Database=C:\\data\\app.db"
      }
    }
  }
}

You can also point MCP Inspector at the executable:

npx @modelcontextprotocol/inspector FireDACMCPServer.exe -conn="DriverID=SQLite;Database=demo.db" -mode=full

Tools

db_info (read-only, idempotent)

Returns the driver id, access mode, row limits and the password-redacted connection string.

list_tables (read-only)

Lists tables and views on any engine (via FireDAC metadata, not engine-specific SQL). Optional schema argument filters by schema.

describe_table (read-only)

Returns each column's name, type, size, nullable and readOnly flags.

  • table (required) — table name, optionally schema-qualified.

run_query (read-only)

Executes a read-only statement (SELECT / WITH / EXPLAIN / SHOW).

  • sql (required) — use :name placeholders for values.
  • params (optional) — JSON object binding those placeholders, e.g. {"id": 42}.
  • max_rows (optional) — capped by the server's -maxrows.

Returns:

{
  "columns": [ { "name": "id", "type": "ftInteger" }, ... ],
  "rows":    [ { "id": 1, "name": "Alice", "age": 30 }, ... ],
  "rowCount": 1,
  "truncated": false
}

run_statement (registered only in read-write / full modes; destructive)

Executes a data- or schema-changing statement.

  • sql (required)INSERT / UPDATE / DELETE / MERGE (read-write) or also DDL (full). Use :name placeholders.
  • params (optional) — JSON object binding those placeholders.

Returns { "success": true, "category": "dml" | "ddl", "rowsAffected": N }.

connect (only in deferred mode — see below)

Opens the database connection when none was configured at startup.

  • connection_string (optional) — a full FireDAC connection string. If given, the discrete parameters below are ignored.
  • driver_id, server, database, user, password (optional) — used to assemble a connection string when connection_string is omitted. An empty user means OS/Windows authentication. For MSSQL, ODBCAdvanced=TrustServerCertificate=yes is added automatically.
  • options (optional) — extra Key=Value;... pairs to append.
  • mode (optional)readonly | readwrite | full (default readonly).

Returns { "connected": true, "driverId": "...", "accessMode": "..." }.


Deferred connection (requesting the connection at runtime)

If you start the server without -conn/-conn-env, it does not fail — it comes up in deferred mode:

  • It exposes the connect tool plus the normal database tools.
  • Until connect succeeds, every database tool returns a clear "No database connection yet. Call the connect tool first." message.
  • The AI (prompted by the user) calls connect with a connection string — or the individual driver/server/database/user/password parameters and a mode — and from then on the database tools operate against that connection.

This lets a single registered server ask for its target database at runtime instead of baking it into the client config. Example client entry:

{
  "mcpServers": {
    "database-on-demand": {
      "command": "C:\\path\\to\\FireDACMCPServer.exe",
      "args": ["-maxrows=500"]
    }
  }
}

Then, in the client: "Connect to the MCPSandbox database on localhost in read-write mode" → the AI calls connect and starts working.

Why a connect tool and not server-initiated elicitation? The MCP spec has an elicitation/create request a server can use to pop a form on the client. That requires a bidirectional transport; the stdio transport used by Claude Desktop is strictly request→response and cannot deliver a server-initiated request (or a tools/list_changed notification). A connect tool is therefore the robust, universally-supported way to request the connection at runtime. On a bidirectional transport you could drive the same flow through TTMSMCPServer.RequestElicitation instead.


Robustness & safety

This demo is built to be a safe bridge, not just a working one:

  • Capability gating by construction. In read-only mode the mutation tool is never registered, so the model cannot even attempt a write.
  • SQL classifier. Every statement is classified after comments and string literals are stripped, so keyword/semicolon scanning cannot be fooled by text inside quotes or comments. run_query accepts only reads; run_statement enforces exactly what the current mode allows.
  • No statement stacking. SELECT 1; DROP TABLE users is rejected unless -allow-multi is set — this blocks a classic piggy-backed injection.
  • Escape statements blocked. ATTACH / DETACH / PRAGMA-writes / VACUUM / GRANT / EXEC … are refused outside full mode.
  • Parameterised values. Callers bind literals through params; values are never string-concatenated into SQL.
  • Bounded output. Result sets are capped (max_rows) and report truncated; large BLOBs are base64-encoded up to a cap, then summarised.
  • Secret hygiene. The connection string is only ever surfaced with its password redacted, and -conn-env keeps it out of process arguments entirely.
  • Resilient connection. AutoReconnect recovers from dropped connections; in read-only mode datasets are also opened read-only as defence in depth.

Recommendations for production use

  • Prefer a dedicated database user whose grants match the chosen mode (a read-only DB login behind readonly mode is belt-and-braces).
  • Pass the connection string via -conn-env, never on the command line.
  • Keep readonly as the default and only raise the mode deliberately.

Extending

  • More engines: add the matching FireDAC.Phys.* unit pair to the uses clause in FireDACMCPServer.dpr.
  • More tools: add a method to TDBConnectionManager and register it in RegisterTools. Follow the existing pattern — classify any SQL through GuardStatement, and marshal results with DatasetToJSON.
  • Transactions / stored procedures / schema resources are natural next steps on top of this foundation.

This demo is part of the TMS MCP SDK and is subject to the TMS Software license agreement.

About

demo for blogpost: https://www.tmssoftware.com/site/blog.asp?post=2498

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages