Universal Database MCP
Allows querying a MariaDB database safely with read-only access, role-based permissions, and data masking.
Allows querying a MySQL database safely with read-only access, role-based permissions, and data masking.
Allows querying a PostgreSQL database safely with read-only access, role-based permissions, and data masking.
Allows querying a SQLite database safely with read-only access, role-based permissions, and data masking.
Click on "Deploy 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., "@Universal Database MCPShow me the schema of the users table in the prod database."
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.
๐๏ธ Universal Database MCP
A safe, role-restricted MCP server for querying your databases from Claude
Query PostgreSQL, MySQL, MariaDB, SQL Server, or SQLite โ read-only, role-restricted, and with sensitive data blacked out โ directly from a Claude conversation.
๐ธ See It In Action
Connected in claude.ai
Per-tool approval controls
Claude asks before it acts
Automatic tenant scoping
Related MCP server: dbridge-mcp
๐งฉ What This Is
A small MCP server that lets an LLM query your databases safely โ 4 files, ~650 lines total, no classes/decorators/async.
File | Role |
| Plain dictionaries: who can see what, which columns get masked. Edit this to change any rule. |
| 4 plain functions: check it's read-only, check the role is allowed, add tenant filtering, mask sensitive values. |
| 3 plain functions: get schema, run query, explain query. Talks to the actual database. |
| The 4 tools the LLM can call, each just a try/except around the functions above, with a log line either way. |
๐ Keeping Database Passwords Out of Chat
db_uri doesn't have to be a raw connection string typed into the conversation:
Copy
.env.exampleto.envand fill in real connection strings there.In chat, refer to a database by its short name โ e.g. "query the
proddatabase" โ and passdb_uri="prod". The server looks upANVAYA_DB_PRODin the environment and uses that.Leaving
db_uriempty ("") usesANVAYA_DEFAULT_DB_URIinstead.
A full connection string (containing ://) still works directly if you pass one โ useful for quick local testing โ but the recommended pattern is to never type a real password into the chat at all.
๐ Authentication (Remote / Multi-Team Deployments)
Since other people connect to this server, role can no longer be trusted as something the LLM just tells the server โ anyone could type role="admin". Instead:
Generate a secret once and put it in
.env:python -c "import secrets; print(secrets.token_hex(32))"ANVAYA_JWT_SECRET=<paste it here> ANVAYA_MCP_TRANSPORT=httpMint a signed token whenever someone needs access:
uv run python issue_token.py --name alice --role analyst --tenant acmeGive them the token. Their MCP client connects with it as a Bearer token. The server verifies the signature on every call and uses the role/tenant from the token โ any
role/tenant_idpassed as arguments is ignored.
If
ANVAYA_JWT_SECRETisn't set, the server runs with no auth at all โ fine for localstdiotesting on your own machine, not for anything reachable by other people.
Transport: this uses Streamable HTTP (transport="http"), the current MCP standard for remote servers โ SSE is deprecated as of the 2025-11-25 MCP spec revision.
โ๏ธ Deploying for Free (Render) So claude.ai Can Reach It
claude.ai connects to remote MCP servers from Anthropic's cloud, not from your browser โ so this needs a real public HTTPS URL. Local network / VPN-only hosting won't work for the web client (Claude Desktop's local stdio config is different and stays working as-is).
Render gives you that for free, with auto-deploy on every git push.
1. Push this project to a GitHub repo
git init
git add .
git commit -m "Universal Database MCP"
git remote add origin https://github.com/YOUR_USERNAME/universal-database-mcp.git
git push -u origin main2. Create the Render service
Go to render.com โ sign up (no credit card needed for the free tier) โ New โ Web Service
Connect your GitHub repo
Settings:
Runtime: Python 3
Build Command:
pip install -r requirements.txtStart Command:
python server.pyInstance Type: Free
3. Add environment variables (Render dashboard โ Environment)
ANVAYA_JWT_SECRET=<your generated secret>
ANVAYA_MCP_TRANSPORT=http
ANVAYA_DEFAULT_DB_URI=sqlite:///test.dbtest.db is committed in this repo as a small demo database โ Render's free tier has no persistent disk, so a database that's part of the codebase is what survives redeploys. For real data, point ANVAYA_DEFAULT_DB_URI at an externally hosted database instead โ e.g. a free Postgres from Neon โ rather than SQLite.
4. Deploy
Render builds and deploys automatically. You'll get a URL like https://your-service.onrender.com. Every future git push to this repo redeploys automatically โ no extra steps needed.
โ ๏ธ Two honest limitations of the free tier:
The service sleeps after 15 minutes of no traffic, and the first request after that takes 30-50 seconds to wake up โ the very first tool call after idling may feel slow or briefly time out.
No persistent disk โ anything written to disk at runtime disappears on the next restart or deploy.
5. Issue a token and add the connector in claude.ai
uv run python issue_token.py --name alice --role analyst --tenant acmeThen in claude.ai: Settings โ Connectors โ Add custom connector
URL:
https://your-service.onrender.com/mcpOpen Request headers (beta feature โ if you don't see it, it may not be rolled out to your account yet)
Header name:
authorizationHeader value:
Bearer <the token you issued>
๐งช Testing With Realistic Data Across All Roles
generate_test_data.py builds a bigger test.db โ 4 tables, 30 rows each, spread across 3 tenants (acme, globex, initech) โ designed to exercise every rule in ROLE_PERMISSIONS:
uv run python generate_test_data.pyThen run the full role test battery:
uv run python test_all_roles.pyThis checks things like: admin can reach every table but still can't write; analyst gets auto-scoped to one tenant and can't see password_hash; support can't see payments at all; readonly_guest can only ever see products.
One thing worth knowing: masking is unconditional โ even
adminseescard_numberredacted, since masking isn't role-aware in this version, only RBAC table/column access is.
If you change test.db, remember to git push so Render picks up the new version on its next deploy (it has no persistent disk, so the committed file is what it actually serves).
โ Before Shipping to a Client
Two things worth doing every time, before a client's database is connected for real:
1. Error messages no longer leak internals. Unexpected errors (a bad connection string, a driver failure, anything not deliberately raised by our own RBAC/validation code) now return only a generic message plus a short reference code โ the real detail goes only to the server-side log (stderr), tagged with that same code. Our own deliberate messages (like "Role 'analyst' is not allowed to access column 'x'") are unaffected and still show clearly, since those are safe by design.
2. Check your RBAC config against the real database before go-live:
uv run python validate_config.py <db_uri or connection name>This catches table/column name mismatches between config.py's ROLE_PERMISSIONS and whatever database you actually point it at โ e.g. a table you renamed but forgot to update in the config, or a table nobody remembered to explicitly allow or deny for a given role. It exits non-zero if it finds a real mismatch, so it's safe to wire into a pre-deploy check if you want.
๐ OAuth Mode (for claude.ai Web)
Bearer tokens (above) work great for Claude Desktop and Claude Code, but claude.ai in a browser currently only offers OAuth as a connector auth option (a "Request headers" beta exists but isn't available to every account yet). For that, this project includes a full, self-hosted OAuth 2.1 authorization server โ a real login page, not just a token check.
Read this before using it in production: building your own OAuth server is something FastMCP's own documentation says most people shouldn't do โ it's included here because claude.ai web specifically requires it and no external identity provider was wanted. The security-critical cryptography (PKCE verification, redirect URI validation) is handled by the underlying MCP SDK, not custom code here โ but you're still responsible for everything else (the login page, code/token bookkeeping, who's allowed to log in). See the limitations listed at the top of
oauth_provider.py.
1. Turn it on
ANVAYA_JWT_SECRET=<your secret>
ANVAYA_AUTH_MODE=oauth
ANVAYA_MCP_TRANSPORT=http
ANVAYA_PUBLIC_BASE_URL=https://your-real-public-url.onrender.com2. Add people who are allowed to log in
uv run python manage_oauth_users.py add --username alice --role analyst --tenant acme
uv run python manage_oauth_users.py list
uv run python manage_oauth_users.py remove --username aliceYou'll be prompted for a password (not shown in your terminal history). This creates oauth_users.json locally โ never commit this file (it's already in .gitignore).
3. Add the connector in claude.ai
Settings โ Connectors โ Add custom connector โ just the URL:
https://your-real-public-url.onrender.com/mcpclaude.ai discovers everything else automatically (it registers itself as an OAuth client, then redirects you to your server's login page). When you connect, you'll see the login form built into this project โ sign in with a username/password from step 2.
What you get, and what you don't
โ You get | โ You don't get |
Real login on claude.ai web, with role/tenant decided by who logged in โ not a header anyone could paste in | No refresh tokens โ when the 24-hour access token expires, the person just logs in again |
PKCE, redirect URI validation, and authorization-code replay protection all verified working | Registered OAuth clients and issued codes are in-memory โ a server restart clears them (claude.ai will just re-register/re-login automatically next time it connects) |
๐ Setup
uv sync
uv run anvaya-mcp๐ ๏ธ The 4 Tools
Tool | Signature |
Get database schema |
|
Execute safe query |
|
Explain SQL query |
|
Export results |
|
๐ฅ Roles
Defined in config.py under ROLE_PERMISSIONS: admin, analyst, support, readonly_guest. Edit that dictionary to add roles, change which tables/columns they can see, or turn tenant filtering on/off.
๐ชถ What Got Simplified From the First Version
Sync database calls instead of async (easier to read top-to-bottom)
No decorators, dataclasses, or Enums โ just dicts and functions
Row-level tenant filtering only supports a single
tenant_idcolumn (not multiple candidate column names)Masking is column-name-based only (no scanning cell contents for patterns like card numbers)
Connections and schema are cached (see db_engine.py โ ENGINE_CACHE and SCHEMA_CACHE, two plain dictionaries), so repeated calls reuse the same connection instead of opening a new one every time, and don't re-fetch the schema on every call. The schema cache refreshes itself every 5 minutes, or immediately if you pass force_refresh=True.
Everything from the original feature list is still here โ schema discovery, relationship mapping, read-only enforcement, EXPLAIN, table/column RBAC, row-level filtering, data masking, CSV/Excel/JSON export, and audit logging โ just written as plainly as possible.
Read-only by design. Role-restricted by default. Nothing sensitive leaves the table.
Available Tools
4 toolsexecute_safe_queryA
Validate and run a read-only SELECT statement. Applies RBAC, tenant row-level filtering, and sensitive-data masking before returning results.
| Name | Required | Description | Default |
|---|---|---|---|
| role | No | One of "admin", "analyst", "support", "readonly_guest". Ignored (and taken from your access token instead) if the server is running with authentication enabled. | |
| db_uri | No | A short connection name configured on the server (e.g. "prod"), a full connection string for local testing, or empty ("") to use the server's default database. | |
| sql_query | No | A single SELECT statement. Anything else is rejected. | |
| tenant_id | No | Required if the role needs row-level filtering. Also taken from your access token when auth is enabled. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden and does so well: it discloses that only a single SELECT is accepted, and that RBAC, tenant row-level filtering, and sensitive-data masking are applied before results return. It omits error behavior on rejection and any rate limits, so it is not complete.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tightly written sentences with zero waste; the action is front-loaded and the security pipeline follows in one compact clause. Every phrase earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description plus a fully documented schema cover the call mechanics. The remaining gap is behavioral edge cases (rejection/error surface, tenant_id requirement conditions) that the schema covers only partially.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all four parameters, including role, db_uri, sql_query, and tenant_id semantics. The description adds nothing parameter-specific beyond the general mention of RBAC and tenant filtering, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Validate and run') and resource ('read-only SELECT statement'), so the agent knows this executes SQL rather than explaining or exporting it. It does not name or contrast with siblings like explain_sql_query, so it falls short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies the tool's context (running read-only SELECTs) but gives no explicit when-to-use guidance against explain_sql_query or get_database_schema, and no stated prerequisites or exclusions. Usage must be inferred from the sibling names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explain_sql_queryA
Return the database's execution plan for a read-only SELECT statement, so query performance can be checked before running it.
| Name | Required | Description | Default |
|---|---|---|---|
| db_uri | No | A short connection name (e.g. "prod"), a full connection string for local testing, or empty ("") for the default database. | |
| sql_query | No | A single SELECT statement to analyze. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It usefully discloses that the tool is non-executing and limited to read-only SELECT statements, which is the key safety trait. It does not cover failure behavior for non-SELECT input, connection/permission requirements, or whether the plan is estimated or analyzed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with the action front-loaded and the rationale trailing it. No filler, no repetition of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter read-only tool with an output schema (so return format need not be described) and full schema coverage, the description is nearly sufficient. The remaining gap is sibling routing and error behavior, both minor given the structured data available.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters (db_uri, sql_query) are already documented in the schema, including the default-connection semantics of an empty db_uri. The description only reinforces the SELECT constraint already stated in the sql_query schema, adding little beyond the structured fields. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: return the execution plan for a read-only SELECT. The clause 'so query performance can be checked before running it' clarifies intent and implicitly separates it from execute_safe_query. However, it never names the sibling it competes with, so differentiation is inferential rather than explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'before running it' implies this is a pre-flight check preceding execute_safe_query, and 'read-only SELECT' bounds acceptable input. But there is no explicit when-to-use/when-not statement and no alternative is named, so the routing guidance is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
export_results_formatB
Run a read-only query (through the same security checks as execute_safe_query) and return it reshaped for a specific use.
| Name | Required | Description | Default |
|---|---|---|---|
| role | No | One of "admin", "analyst", "support", "readonly_guest". Ignored (and taken from your access token instead) if the server is running with authentication enabled. | |
| db_uri | No | A short connection name (e.g. "prod"), a full connection string for local testing, or empty ("") for the default database. | |
| sql_query | No | A single SELECT statement. | |
| tenant_id | No | Required if the role needs row-level filtering. Also taken from your access token when auth is enabled. | |
| format_type | No | One of "csv", "excel", or "chart_json" |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does disclose two useful traits: the operation is read-only and it passes through the same security checks as execute_safe_query. It still omits the behavioral specifics that matter for an export tool, such as size limits, format availability, or how results are returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence with the action front-loaded and no wasted words. It is arguably too terse for what the tool needs to convey, but as a structure/conciseness measure it is efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and the 100%-covered input schema handles parameters. What is missing is the usage differentiation from execute_safe_query; for a tool whose only apparent distinction is output shape, that gap leaves the definition minimally adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all five parameters (role, db_uri, sql_query, tenant_id, format_type) are already documented in the schema. The description adds nothing beyond that baseline, not even a mention of the format options it implies with 'reshaped for a specific use'.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Run a read-only query ... return it reshaped'), and references execute_safe_query to signal the relationship to a sibling. However, 'reshaped for a specific use' is vague and never names the actual export formats (csv/excel/chart_json) that distinguish this tool from plain execute_safe_query.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance, no conditions, and no exclusions. Given that execute_safe_query appears to be the near-identical sibling (same security checks, same read-only query), the description never tells the agent when to pick this tool instead of that one, which is the single most important routing decision here.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_database_schemaA
Discover tables, columns, primary keys, and foreign-key relationships, filtered to only what your role is allowed to see.
| Name | Required | Description | Default |
|---|---|---|---|
| role | No | One of "admin", "analyst", "support", "readonly_guest". Ignored (and taken from your access token instead) if the server is running with authentication enabled. | |
| db_uri | No | Either a short connection name configured on the server (e.g. "prod", "reporting" โ these map to environment variables like ANVAYA_DB_PROD, so no password is ever typed here), a full connection string for local testing (e.g. sqlite:///path/to/file.db), or left empty ("") to use the server's default database. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full behavioral disclosure burden. It mentions role-based filtering but fails to describe output format, permissions required, potential errors, or how results are structured. The output schema exists but that doesn't excuse the lack of behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, well-structured sentence that front-loads the key action and enumerates returned data. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that there are no annotations and an output schema exists, the description should ideally explain more about behavior, such as caching, performance, or error handling. It covers the core purpose adequately but lacks depth for a tool with zero annotations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already fully documents both parameters. The description adds no parameter-specific information beyond what is in the schema, which meets the baseline of 3 for complete schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (Discover) and enumerates the exact resources returned (tables, columns, primary keys, foreign-key relationships). It clearly distinguishes itself from sibling tools like execute_safe_query, which run queries rather than introspect schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context via 'filtered to only what your role is allowed to see', suggesting it should be used to explore permitted schema. However, it provides no explicit when-to-use or when-not-to-use guidance relative to siblings such as explain_sql_query or execute_safe_query.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v1.0.0- First observed
execute_safe_query - First observed
explain_sql_query - First observed
export_results_format - First observed
get_database_schema
TDQS
Scored across 4 tools
get_database_schema and explain_sql_query are clearly distinct from each other. However, export_results_format and execute_safe_query both run read-only queries, and the difference (reshaping/output format vs. raw results) is only clear from the description, creating mild overlap.
All names use snake_case with a verb-first pattern (export_, get_, execute_, explain_), which is predictable and readable. export_results_format is slightly awkward (verb_noun_noun) but still follows the convention.
Four tools is lean but well-scoped for a read-only, safety-focused query server; each tool covers a distinct capability (schema, query, plan, export). It sits just above the thin end of the range but earns its place.
The read-only lifecycle is well covered: discover schema, inspect plans, run queries, and export results. Gaps are minor, such as no way to list available databases/connections, but the stated read-only purpose is served without dead ends.
Maintenance
Related MCP Connectors
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
- mcpOAuthcom.gibsonai
GibsonAI MCP server: manage your databases with natural language
Query your org's data in natural language โ read-only MCP access to SQL, NoSQL, files & warehouses.
Draxlr's remote MCP server connects AI assistants to your SQL databases and dashboards. Explore schemas, run read-only queries, manage saved queries and dashboards, and export results, all with row-level security so each user sees only their own data.
Related MCP Servers
- AlicenseAqualityAmaintenanceRead-only MCP server for querying PostgreSQL, MySQL, and SQLite from AI agents โ multi-database, safe by default.415 npm1ISC
- AlicenseAqualityAmaintenanceRead-only MCP server that lets AI agents safely query SQLite, PostgreSQL, and MySQL/MariaDB. Enforces read-only transactions with column masking, row caps, query timeouts, EXPLAIN-based cost rejection, and rate limiting.724 npm1MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that provides safe, read-only SQL access for AI agents to query databases (PostgreSQL, MySQL, SQLite) with schema awareness and guardrails.12 npmMIT
- FlicenseNot gradedqualityBmaintenanceAn MCP server that exposes PostgreSQL/MySQL/SQLite/MongoDB databases to AI agents, converting natural language questions into SQL queries with configurable LLM providers and safe read-only operations.-