Skip to content

Repository files navigation

MCP Database Query App

A mature Model Context Protocol app — not just a server — written in C# / .NET 10 that lets LLM clients manage PostgreSQL and SQL Server databases through Dapper, with a full interactive UI rendered directly inside the client.

MCP Database Query App — interactive chart rendered inside the MCP client

Ask the model for a query. It runs the SQL, streams back results, and opens a live, sortable grid / filterable schema viewer / Chart.js visualisation inside your MCP client via the MCP Apps protocol — no separate window, no screenshots, no context loss.

Highlights

  • MCP App, not just an MCP server — ships a results grid, SQL builder, schema explorer, and Chart.js charting as MCP UI resources that hosts with MCP UI support (e.g. Claude Desktop) render inline.
  • Read-only by default — writes and DDL are blocked unless a session explicitly opts in, and opting in is itself confirmed. Any statement that changes the database — INSERT and CREATE as much as DROP — asks for confirmation before running.
  • Encrypted credential storage — saved database passwords are encrypted with AES-256-GCM using a master key you control.
  • Pagination & limits — every query has a hard row cap with cursor-based paging for large result sets.
  • Both transports — stdio (default) or HTTP/SSE.

Quick install

Option 1 — Drop-in .mcpb extension (Claude Desktop)

Grab the bundle for your platform and open it in Claude Desktop. The .mcpb is a self-contained single-file package of the server, its .NET runtime, and the UI assets — no dotnet or Node install on the host. .mcpb is a Claude Desktop format; Claude Code and other hosts should use Option 2.

Platform Download
Windows x64 mcp-database-query-app-win-x64-jit.mcpb
Windows ARM64 mcp-database-query-app-win-arm64-jit.mcpb
macOS (Apple Silicon) mcp-database-query-app-osx-arm64-jit.mcpb
Linux x64 mcp-database-query-app-linux-x64-jit.mcpb
Linux ARM64 mcp-database-query-app-linux-arm64-jit.mcpb

Double-click the .mcpb, or drag it into Claude Desktop's Settings → Extensions. On first install, Claude Desktop will prompt for the user-config values declared in manifest.json (master key, read-only toggle, max result limit, UI toggle). Read-only is on by default.

Option 2 — mcpServers JSON config

For Claude Code, Cursor, Windsurf, or any other host that reads an mcpServers block, point command at the extracted server binary (from a .mcpb or a dotnet publish output):

{
  "mcpServers": {
    "database": {
      "command": "/absolute/path/to/mcp-database-query-app",
      "env": {
        "McpDatabaseQueryApp__Transport__Stdio__Enabled": "true",
        "McpDatabaseQueryApp__Transport__Http__Enabled": "false",
        "McpDatabaseQueryApp__ReadOnlyByDefault": "true",
        "McpDatabaseQueryApp__MaxResultLimit": "5000",
        "McpDatabaseQueryApp__Ui__Enabled": "true",
        "McpDatabaseQueryApp__Secrets__KeyRef": "ENV:MCP_DATABASE_QUERY_APP_MASTER_KEY",
        "MCP_DATABASE_QUERY_APP_MASTER_KEY": "<base64-encoded 32+ byte secret>"
      }
    }
  }
}

On Windows, point command at mcp-database-query-app.exe. Generate a master key with openssl rand -base64 32.

Claude Code also accepts the same server via CLI — the flags below produce an equivalent entry in your project's .mcp.json:

claude mcp add database \
  --env McpDatabaseQueryApp__ReadOnlyByDefault=true \
  --env McpDatabaseQueryApp__Ui__Enabled=true \
  --env McpDatabaseQueryApp__Secrets__KeyRef=ENV:MCP_DATABASE_QUERY_APP_MASTER_KEY \
  --env MCP_DATABASE_QUERY_APP_MASTER_KEY="$(openssl rand -base64 32)" \
  -- /absolute/path/to/mcp-database-query-app

Features

  • Connection management — open, ping, disconnect, and save pre-defined connection profiles.
  • Querying — parameterised SELECTs, paged results, non-query execution, and EXPLAIN plans. Any query tool can return its result as a short pre-authenticated HTTP link instead of inline JSON (output: "link"), keeping large results out of the model's context.
  • Schema introspection — list schemas, tables, columns, keys, indexes, roles, and databases, with batched describe calls.
  • Saved scripts & notes — store SQL snippets and attach notes to database objects.
  • Interactive UI — sortable/filterable results grid, SQL builder, schema viewer, and charting, all rendered inside the MCP host.
  • Guided promptsexplore-database, safe-write, migrate-script, explain-slow-query seed the model with a workflow.
  • Auto-completion — resource and prompt-argument completion backed by a per-connection metadata cache.
  • Confirmations — every write is gated behind an MCP elicitation prompt, with destructive statements (DROP, TRUNCATE, unqualified DELETE/UPDATE) called out explicitly in the prompt. Handing out write access in the first place is gated too: db_connect with readOnly: false, db_predefined_create with readOnly: false, and db_predefined_update turning read-only off each ask before they take effect.

Configuration

appsettings.json controls process-wide behaviour. Runtime metadata (pre-defined connections, saved scripts, cached result sets) is stored in a SQLite database under %APPDATA%/McpDatabaseQueryApp/mcp-database-query-app.db. Passwords are encrypted with AES-256-GCM using a key resolved from McpDatabaseQueryApp:Secrets:KeyRef (UserSecrets:…, Env:…, File:…, or Literal:…).

{
  "McpDatabaseQueryApp": {
    "MetadataDbPath": "%APPDATA%/McpDatabaseQueryApp/mcp-database-query-app.db",
    "DefaultResultLimit": 500,
    "MaxResultLimit": 50000,
    "AllowDisableLimit": true,
    "ReadOnlyByDefault": true,
    "Transport": {
      "Stdio": { "Enabled": true },
      "Http":  { "Enabled": false, "Urls": "http://127.0.0.1:5218" }
    },
    "Ui": { "Enabled": true },
    "ResultLinks": {
      "Enabled": true,
      "Ttl": "02:00:00",
      "BaseUrl": null,
      "MaxEntries": 500
    },
    "Secrets": { "KeyRef": "UserSecrets:McpDatabaseQueryApp:MasterKey" }
  }
}

Every setting above is overridable via environment variable using the double-underscore convention (McpDatabaseQueryApp__ReadOnlyByDefault=true).

Result links (output: "link")

db_query, db_query_next_page and db_explain take an optional output argument:

output Behaviour
"inline" Default, and identical to previous versions: the result is in the response.
"link" The same JSON is parked on the server; the response carries only resultUrl.
// db_query with output: "link"
{
  "connectionId": "conn_abc123",
  "columns": [ { "name": "id" }, { "name": "email" } ],
  "rows": [],
  "rowCount": 12043,
  "resultUrl": "https://db.example.internal/r/1a2B3c4D5e6F7g8H9i0JkLmNoPqRsTuVw",
  "resultUrlExpiresAt": "2026-09-08T12:00:00+00:00"
}

GETting that URL returns exactly the JSON output: "inline" would have produced, with the rows populated. Notes:

  • The URL is the credential. It carries 128 bits of entropy and is not checked against any profile, ACL or bearer token — anyone holding the link can read that one result until it expires. Only mint links for results the recipient is allowed to see, and prefer HTTPS so the token is not on the wire in clear text.
  • Links live in memory for ResultLinks:Ttl (2 hours by default) and are gone after a restart. Unknown, malformed and expired tokens are all a plain 404.
  • ResultLinks:MaxEntries (500 by default) caps memory; reaching it evicts the entries closest to expiry.
  • Set ResultLinks:BaseUrl whenever the server sits behind a reverse proxy — without it the URL is built from the inbound request, which is wrong as soon as the proxy rewrites host or scheme.
  • The row limit works exactly as it does inline: a link contains the rows the inline call would have returned, so raise limit (or limit: 0 with confirmUnlimited) to put more of them behind the URL.
  • output: "link" requires the HTTP transport and is refused with an actionable error under stdio unless ResultLinks:BaseUrl points at a reachable host.

Seeding pre-defined connections

Normally connections are saved interactively (db_predefined_create, or the UI). For headless deployments — an MCP server launched by an orchestrator with no operator to click through setup — you can declare connections in configuration under McpDatabaseQueryApp:Connections. They are encrypted and upserted into the metadata store at startup, so the model can immediately db_connect to them by name.

{
  "McpDatabaseQueryApp": {
    "Secrets": { "KeyRef": "ENV:MCP_DATABASE_QUERY_APP_MASTER_KEY" },
    "Connections": [
      {
        "Name": "scraper",
        "Provider": "Postgres",
        "Host": "db.internal",
        "Port": 5432,
        "Database": "etsy_listings",
        "Username": "etsy",
        "Password": "etsy_secret",
        "SslMode": "Disable",
        "ReadOnly": true,
        "DefaultSchema": "es",
        "Tags": ["scraper"]
      }
    ]
  }
}

The same, via environment variables (note the indexed __0__ segments):

McpDatabaseQueryApp__Connections__0__Name=scraper
McpDatabaseQueryApp__Connections__0__Provider=Postgres
McpDatabaseQueryApp__Connections__0__Host=db.internal
McpDatabaseQueryApp__Connections__0__Port=5432
McpDatabaseQueryApp__Connections__0__Database=etsy_listings
McpDatabaseQueryApp__Connections__0__Username=etsy
McpDatabaseQueryApp__Connections__0__Password=etsy_secret
McpDatabaseQueryApp__Connections__0__SslMode=Disable
McpDatabaseQueryApp__Connections__0__ReadOnly=true
McpDatabaseQueryApp__Connections__0__DefaultSchema=es

Seeding from a connection string

If you'd rather hand the server a full connection string, set McpDatabaseQueryApp:ConnectionStrings:<name> — the same shape as the conventional ConnectionStrings section, but namespaced under this app so it never collides with (or silently inherits) a host process's top-level ConnectionStrings. Each entry becomes a read-only connection named after its key, and the provider is inferred from the keywords:

# env: a Host= keyword implies Postgres; Server=/Data Source= without Host implies SQL Server
McpDatabaseQueryApp__ConnectionStrings__scraper=Host=db.internal;Port=5434;Database=etsy_listings;User ID=etsy;Password=etsy_secret;SSL Mode=Disable
McpDatabaseQueryApp__Secrets__KeyRef=ENV:MCP_DATABASE_QUERY_APP_MASTER_KEY
{
  "McpDatabaseQueryApp": {
    "ConnectionStrings": {
      "scraper": "Host=db.internal;Port=5434;Database=etsy_listings;User ID=etsy;Password=etsy_secret;SSL Mode=Disable"
    }
  }
}

For finer control alongside a connection string — read-write, a default schema, an explicit provider, or tags — put the connection string on a Connections entry instead; discrete fields set on the same entry take precedence over the parsed ones:

{
  "McpDatabaseQueryApp": {
    "Connections": [
      {
        "Name": "scraper",
        "ConnectionString": "Host=db.internal;Database=etsy_listings;Username=etsy;Password=etsy_secret",
        "ReadOnly": true,
        "DefaultSchema": "es"
      }
    ]
  }
}

Notes:

  • A master key is required. Seeded passwords are encrypted with the key resolved from McpDatabaseQueryApp:Secrets:KeyRef. If that key is missing, the affected entry is logged and skipped (the server still starts).
  • Seeding is idempotent — entries are keyed by Name, so restarting refreshes existing rows rather than duplicating them. Edit the config and restart to update a connection.
  • ReadOnly defaults to true. SslMode defaults to the provider's natural default when neither a discrete field nor the connection string specifies it (Prefer for Postgres, Require/Mandatory for SQL Server); set SslMode=Disable (Postgres) or Encrypt=false (SQL Server) for databases without TLS.
  • Provider inference is a heuristic: a Host keyword ⇒ Postgres; Server / Data Source without Host ⇒ SQL Server; otherwise it falls back to Postgres. Set Provider explicitly to override (e.g. a Postgres string that uses Server=).
  • Required per connection (from discrete fields, the connection string, or a mix): Name, provider, Host, Database, Username, Password. A malformed entry is skipped without aborting the others.

Build from source

Requires the .NET 10 SDK (10.0.201+) and Node.js 18+.

dotnet build
dotnet test

# stdio (default)
dotnet run --project src/McpDatabaseQueryApp.Server

# HTTP/SSE
McpDatabaseQueryApp__Transport__Http__Enabled=true dotnet run --project src/McpDatabaseQueryApp.Server

License

MIT

About

MCP App for Querying and Managing Databases (with UI support in e.g. Claude Desktop!)

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages