A standalone MCP server that gives AI agents read-only, scope-controlled, fully audited access to production data across many products, services, and databases.
- Full design:
mcp-data-service-design.md - Implementation spec:
SPEC.md
One uniform tool (query) over MySQL + MongoDB sources. Agents hold a scoped API key, receive a pre-filtered registry (they can't even enumerate what they can't reach), and every call is validated against scope and written to an audit store. Read-only is enforced twice: at the query guard and by the DB credential itself.
QueryMesh has its own MySQL database that acts as both the config service and the audit store — three tables (src/store/schema.sql):
registry_nodes— theProject → Service → Databaseregistry (adding a source is an INSERT, not a deploy)api_keys— key id → label + scopes + HMAC hash (never the raw key)audit_log— every tool call, 180-day retention
This DB is separate from any data source it fronts. Source credentials come from the env secret backend (QM_SECRET_*), never from the config service.
npm install
cp .env.example .env # set QM_DB_*, QM_KEY_PEPPER, QM_SECRET_*
# One-time: create the service database + least-privilege user (run as a MySQL
# admin, NOT as the querymesh user). Edit the password in the file to match .env.
mysql -u root -p < src/store/bootstrap.sql
npm run migrate # create the three tables (assumes the DB + user exist)
npm run seed # optional: example registry nodes for local dev
npm run issue-key -- --label "daily-summary-agent" --scopes edumix.api,edumix.eduvid.videocdn
npm run dev
Access denied for user 'querymesh'@'localhost'? The DB/user weren't created, or the password doesn't matchQM_DB_PASSWORD. Runbootstrap.sqlas root (above), orALTER USER 'querymesh'@'localhost' IDENTIFIED BY '<your password>';..envis loaded automatically; real shell env vars, if set, take precedence over it.
Point an MCP client at POST http://localhost:8080/mcp with Authorization: Bearer <key> (the raw key printed by issue-key).
QueryMesh is its own OAuth 2.1 Authorization + Resource Server, so it can be added as a custom connector in claude.ai / Claude Desktop (Settings → Connectors → Add custom connector). Two requirements:
- Public HTTPS URL. The connector UI can't reach
localhost— expose the server via a tunnel (cloudflared tunnel --url http://localhost:8080orngrok http 8080) and setQM_PUBLIC_URLto thathttps://…origin (it's the OAuth issuer). Registerhttps://…/mcpas the connector URL. - Authorize with an API key. During the OAuth flow QueryMesh shows a login page; paste a key issued by
issue-key. The access token is bound to that key's scopes.
Raw Authorization: Bearer <key> still works for the CLI (claude mcp add --transport http … --header) and curl — both auth paths share one verifier.
npm test # guard + scope adversarial tests
npm run typechecksrc/
index.ts entry: migrate, load config service, start Express edge, retention purge
http/edge.ts Express: OAuth router + bearer auth -> per-principal MCP server (Streamable HTTP)
server.ts MCP server: injects scoped registry, registers the query tool
store/
schema.sql service DB schema (registry_nodes, api_keys, audit_log, oauth_*)
db.ts service MySQL pool + migration runner
config-store.ts loads + hot-reloads registry/keys from the service DB
config/ Zod schemas + SecretResolver (env backend)
registry/ Project->Service->Database model, scope filtering, safe injection view
auth/ HMAC key hashing, key resolution, boundary-aware scope matching
oauth/ OAuth 2.1 provider (DB-backed) + interactive login page
connectors/ Connector interface, pool mgmt, MySQL/Mongo impls + read-only guards
guards/ mysql-guard (single SELECT), mongo-guard (block $out/$merge/JS + allowlist)
tools/ query tool + shared context
audit/ buffered logger + MySQL sink (with retention purge)
cli/ migrate, seed, issue-key, add-node (interactive)
test/ guard + scope adversarial tests
Run the interactive CLI — it prompts for each field, validates against the same Zod schema the server uses, previews the node, upserts it, and prints the QM_SECRET_* env var to set:
npm run add-node(Or INSERT directly into registry_nodes.) Then add the matching QM_SECRET_* to the env backend and grant an API-key scope. The config service hot-reloads within CONFIG_REFRESH_MS. No code touched, no deploy.
Skeleton / scaffolding — typechecks and unit tests pass. Before production (see SPEC §11): per-key rate limiting, audit-failure metric, /readyz pool health, and an integration suite proving writes are refused at both the guard and credential layers.