BridgeMCP
Provides read-only SQL query capabilities for SQLite databases, with table allow-lists, automatic masking of sensitive columns, query limits, and full audit logging.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@BridgeMCPwhat was the average order value by channel last month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
BridgeMCP · Turn business data into an MCP service Claude can use
Register a SQLite file or a folder of CSVs and it instantly becomes four read-only tools that Claude Desktop / Claude Code can call directly.
Your database credentials never leave your server, and every query the AI runs is logged.

What problem it solves
Your team is already using Claude, and the business side keeps asking "which channel had the highest average order value last month" — and every time, someone on the engineering side has to hand-write SQL and export a spreadsheet.
You'd love for the AI to just query it directly, but you can't hand over database credentials: one stray DELETE, or one full-table export, and you have an incident on your hands.
BridgeMCP sits in between as a gateway:
No connection details ever leave your server — the database path and credentials live only on your own machine; Claude receives four restricted tools, not a database connection
Read-only, enforced three separate times — SQL parsing, a read-only connection, and a SQLite authorizer callback; any single layer failing doesn't open the door to unauthorized access
Tables you don't want seen are invisible, not just hidden — anything outside the table allow-list doesn't even show up in
list_tables; querying it directly is rejectedPhone numbers and emails are masked automatically — columns are flagged as sensitive by name at registration time and always come back masked; wrapping them in an alias or a function to dodge masking gets rejected outright
No single call can walk off with the whole database — row count, byte size, and execution time are all capped, so even a full cross-join gets truncated instead of taking down the database
Every AI query is on the record — each call is logged to the dashboard with the exact SQL, row count, and duration; rejected calls are highlighted red with the reason spelled out
The security boundary (this is the entire point of the project)
Every row below is something you can trigger live from the dashboard's "Tool console":
What the AI tries | Result |
| Rejected · only read-only queries are allowed; statement starts with UPDATE |
| Rejected · only a single statement is allowed; multiple statements detected |
| Rejected · table staff_salaries is not in the allow-list |
| Allowed · |
| Rejected · reads a masked column without emitting it under its original name |
| Rejected · same reason — functions and expressions can't dodge masking either |
| Rejected · masked columns can't be used as a search condition, or you could reverse-lookup them |
| Rejected · sqlite_master is not in the allow-list |
| Rejected · statement contains a forbidden operation: recursive CTE |
A 46 × 85-row cross join | Allowed, but truncated · only the first 200 rows come back, flagged as truncated |
A query that never finishes | Aborted · exceeds the 3000 ms execution limit, with a "narrow your scope" suggestion attached |
The three layers of defense split the work like this:
Parse layer — strips comments and string literals, then checks keywords and statement count.
SELECT '-- drop table'isn't falsely flagged, andSELECT 1 /* */ ; DROP TABLE tcan't hide either.Connection layer — the SQLite file is opened with a
file:...?mode=roread-only URI; writes fail at the driver level.Authorization layer — SQLite calls back on every single action as it compiles a statement; only SELECT and READ are allowed, and READ is checked column-by-column against the allow-list. ATTACH, PRAGMA, CREATE, and
load_extensionare all denied. This layer doesn't depend on our regex being airtight.
The pragma_table_list example above is the proof: it slips past the keyword matcher (pragma_table_list isn't pragma), but the authorization layer still blocks it — as an "unlisted table," not a banned keyword.
Call auditing
Every call — including rejected ones — is written to the database: tool name, full arguments, status, rejection reason, row count, byte size, duration, and whether it came from MCP or HTTP. Rejected rows show up highlighted red on the dashboard, with the reason written directly in the row — no hovering required. This is the part meant for your client to see: exactly what the AI queried, at a glance.
When you hand data to an AI, the client's real concern is never "can it query this" — it's "will I be able to see what it queried." The audit detail lays out the full SQL for every call, which tables and columns it touched, how many rows came back, and how long it took. Rejected requests spell out the reason too — not a vague "auditing enabled" badge, but something you can actually hand to a client and have them read every line themselves.

Related MCP server: QueryForge
Quick start
git clone https://github.com/LuciferLiu/bridgemcp.git
cd bridgemcp
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # adjust row/byte/timeout caps if you want
python seed_demo.py # generates a fictional e-commerce DB + an audit log
uvicorn app.main:app --port 8050The demo data is a fictional e-commerce business database (customers / orders / order items, including phone numbers and emails), plus a staff_salaries table that's deliberately kept hidden from the AI to demonstrate the allow-list.
Everything runs fully offline — no API key required.
Connecting to Claude
The "Connect to Claude" section on the dashboard generates a config with your absolute path already filled in — just copy it. The example below uses /path/to/bridgemcp; swap in your actual local path, or better, use whatever the dashboard shows you:
{
"mcpServers": {
"bridgemcp": {
"command": "/path/to/bridgemcp/.venv/bin/python",
"args": ["-m", "app.mcp_server"],
"cwd": "/path/to/bridgemcp"
}
}
}Save it to ~/Library/Application Support/Claude/claude_desktop_config.json and restart Claude Desktop.
For Claude Code, one command does it:
claude mcp add bridgemcp -- /path/to/bridgemcp/.venv/bin/python -m app.mcp_serverFrom there, just ask "which channel had the highest average order value last month" in the chat. Claude will walk through
list_tables → describe_table → query on its own, and every step shows up in the dashboard's audit log.
Registering your own data source
curl -X POST http://localhost:8050/api/sources \
-H "Content-Type: application/json" \
-d '{
"name": "erp",
"kind": "sqlite",
"path": "/data/erp.db",
"description": "ERP read-only replica"
}'Registration auto-detects the schema and flags columns like phone / email / id_card as masked based on their name.
From there, use the dashboard to flip each table visible or hidden. For CSVs, set kind to csv and point path at a directory —
each .csv file inside becomes one table.
Docker
cp .env.example .env
docker compose up -dIn docker-compose.yml, business data is mounted :ro — the container never gets write access either.
Configuration
Everything is controlled via environment variables; see .env.example:
Variable | Default | Description |
|
| Metadata DB (source registry + audit log); relative paths resolve against the project root |
|
| Max rows returned per query — even if the AI passes a larger |
|
| Byte cap per result; anything past it is dropped and flagged |
|
| Max execution time for a single query; aborted on timeout |
|
| At registration, any column name matching one of these fragments is auto-flagged as masked |
|
| Port for the dashboard and HTTP debug API |
The three caps are read at request time — change them and restart, no need to re-register any data source.
Architecture
app/
├── main.py FastAPI routes: dashboard API + HTTP debug endpoint
├── mcp_server.py MCP server entrypoint (stdio), python -m app.mcp_server
├── tools.py The four tool implementations + per-call audit logging
├── guard.py The security boundary: SQL parsing, read-only connection, authorizer, caps, masking
├── reason_i18n.py Bilingual templates for rejection reasons, shared by audit logging and the dashboard
├── introspect.py Data source introspection (SQLite / CSV); CSVs load into an in-memory DB
├── models.py SQLAlchemy models
├── schemas.py Pydantic request/response validation
├── config.py Environment variables
├── db.py Engine & session (with PRAGMA foreign_keys=ON)
└── static/
└── index.html The dashboard (vanilla JS, no build step, ships with an EN/中文 toggle)Three trade-offs were made deliberately:
Security doesn't hinge on "parsing SQL correctly." Any regex-based read-only check can be beaten by a sufficiently creative query.
So the real backstop is SQLite's own authorizer callback — it approves or denies each action at compile time, and it doesn't care whether our regex missed something.
The cost is that this mechanism is tied to SQLite; porting to Postgres would mean swapping it for a read-only role plus the equivalent of SET TRANSACTION READ ONLY.
What you get in return: even if the parse layer is bypassed, unauthorized access still can't happen.
Masked columns wrapped in an alias or function are rejected outright, rather than best-effort masked. Masking can only match by output column name — SELECT substr(phone,1,7) produces an output name that doesn't match, and "best-effort" masking that can be dodged is functionally no masking at all.
So the model is forced to rewrite the query instead — and the rejection message already tells it how. A handful of legitimate queries get caught in the net, but masking isn't something that gets to be probabilistic.
CSV sources are loaded into an in-memory SQLite database on every query. A separate pandas-style filtering path for CSVs was an option, but that would mean writing the security logic twice — and the second copy is exactly where a hole would end up. Reusing the same guard matters more.
The cost is that this doesn't scale to CSVs in the hundreds of megabytes — at that point, convert to SQLite first and register that instead.
MCP tools
Tool | Arguments | Description |
|
| Lists visible tables, with row counts, column names, and masked columns |
|
| Column structure, types, masking flags, plus 3 masked sample rows |
|
| Executes a single read-only SELECT / WITH statement |
|
| Fuzzy keyword match across text columns; masked columns are excluded from search |
source can be omitted when only one data source is registered; with multiple sources it must be given explicitly,
otherwise you'll get a rejection listing the available data source names.
HTTP API
Interactive docs are available at /docs once the server is running.
Method | Path | Description |
|
| Dashboard summary: source count, visible table count, 24h calls, 24h rejections |
|
| Currently effective row / byte / timeout caps |
|
| List of data sources, including each table's schema and exposure policy |
|
| Register a data source (schema is introspected immediately; a failed introspection isn't persisted) |
|
| Re-introspect the schema, preserving the existing allow-list and masking config |
|
| Delete a data source and its table metadata |
|
| Update a table's |
|
| Call audit log |
|
| Generates a Claude Desktop config with the absolute path already filled in |
|
| Try a tool from the browser; runs through the exact same execution and audit path as MCP |
Example debug call:
curl -X POST http://localhost:8050/api/tools/query \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT channel, ROUND(SUM(amount),2) AS gmv FROM orders GROUP BY channel ORDER BY gmv DESC"}'POST /api/tools/{name} returns 200 even for rejected calls, with {"error": "...", "rejected": true} in the body.
That's deliberate: "rejected by security policy" is a normal business outcome, not an HTTP-layer error — and it needs to land in the audit table exactly like a successful call does. Even an unknown tool name gets logged; "someone is probing with the wrong tool name" is itself worth keeping a record of.
Notes
This is a gateway, not a database proxy. It only does table-level allow-listing and column-level masking — there's no row-level permission model (e.g. "sales reps can only see their own customers"). If you need row-level isolation, build a view in your business database and register only the view.
Point this at a read-only replica or a read-only account. BridgeMCP opens the file read-only on its own, but the first layer of defense in depth is always "never grant write access in the first place." The metadata database (registry + audit log) and the business database are two separate databases and never get written to interchangeably.
Auditing is written synchronously. Every tool call costs one extra write, in exchange for the guarantee that "the log always exists alongside the result." If call volume outgrows what SQLite can handle, swap DATABASE_URL for Postgres, or switch auditing to an async batched write.
Rejection messages shown to the model are deliberately verbose. Every rejection tells the model exactly how to rewrite the query — otherwise it just keeps retrying with random variations, generating a dozen useless audit entries in the process.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityAmaintenanceA secure, read-only MCP server that empowers Claude Desktop and AI agents to safely query and inspect local SQLite databases.Last updated
- Flicense-qualityDmaintenanceA unified MCP server that lets Claude query any SQLite database and build live Streamlit dashboards — all from a single conversation.Last updated1
- AlicenseAqualityAmaintenanceA read-only MCP server that exposes SQL database access to LLMs, supporting multiple database types, compact columnar results, pagination, and file export.Last updated625MIT
- Flicense-qualityCmaintenanceEnterprise-safe MCP server that enables Claude Enterprise to access approved on-prem SQL Server data through read-only, governed, and auditable tools.Last updated
Related MCP Connectors
Hosted Amazon Seller Central and Amazon Ads MCP server for Claude, ChatGPT, Cursor, and agents.
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/LuciferLiu/bridgemcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server