Repository navigation
Show and tell: Hardening TypeScript MCP Database Tools — Walkthrough on Read-Only Guardrails, CTE Defenses & Stdio Isolation #6601
A7mdMe
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Context & Maintainer Disclosure
Maintainer of MCP Production Kit (an open-source TypeScript Model Context Protocol server starter kit).
As more TypeScript developers connect AI agents (Claude Desktop, Cursor, Cline, OpenWebUI) to relational databases via Model Context Protocol (MCP) tools, standard tutorials suggest passing raw SQL straight to a database connector tool. In development this works, but in production it exposes the database to severe risks: hallucinated mutations, prompt-injected DDL, and context exhaustion.
We wanted to share a concrete end-to-end walkthrough on how to harden TypeScript MCP database tools, along with verified test benchmarks and architectural boundaries.
1. Three Hidden Failure Modes in Agent Database Tools
An agent reading untrusted web scrape data or customer tickets can be tricked by indirect prompt injection into running:
A naive prefix regex checking only for
^SELECTwill be bypassed by a data-modifying CTE:WITH, yet it executes a destructive mutation.When database client libraries or ORMs print debug logs or query execution times via
console.log, stdout is contaminated with raw strings. Under MCP's stdio transport, this corrupts JSON-RPC 2.0 message framing and crashes the client connection.2. Layered Defense Architecture
(Note: This application-level guardrail uses token & regex sanitization, not a full SQL AST compiler, and is engineered specifically to accompany database-level read-only permissions like
GRANT SELECT).3. Core Implementation: Safe Query Inspection
Here is the exact TypeScript inspection routine:
4. Verified Automated Test Matrix
We run an automated test harness covering both benign queries and common injection patterns:
SELECT id, name FROM users WHERE active = true-> PASSSELECT * FROM orders WHERE user_id = $1-> PASSWITH t AS (SELECT count(*) as c FROM orders) SELECT * FROM t-> PASSSELECT * FROM audit_log WHERE action = 'DROP TABLE'-> PASS (string literals normalized)DELETE FROM users WHERE id = 1-> BLOCKEDDROP TABLE analytics_events-> BLOCKEDSELECT * FROM users; DROP TABLE users;-> BLOCKED (multiple statements rejected)WITH d AS (DELETE FROM users RETURNING *) SELECT * FROM d-> BLOCKED (CTE mutation detected)/* comment */ DROP TABLE users-> BLOCKED (comment stripped)5. Architectural Comparison: Free Core vs. Pro Pack Add-on
Includes the full JSON-RPC 2.0 dual transport (stdio + SSE), zero-leak stderr streaming, diagnostic system telemetry, SSRF-guarded HTTP fetch tool, in-memory KV state store, and standard Dockerfile.
For engineering teams deploying to cloud production, includes pre-packaged
postgres_connector.ts, Cloudflare Workers serverless edge template, and hardened multi-container Docker Compose with 26 passing automated tests.Questions & Technical Objections for the Community
We would love feedback and critique from TypeScript database engineers:
$$...$$, custom procedural functions, or transaction control likeROLLBACK) do you feel need deeper handling beyond regex tokenization?Full walkthrough guide: examples/SAFE_POSTGRES_MCP_WALKTHROUGH.md
All reactions