Server Implementation
A tool handler interpolates model-supplied input into an SQL string, so the LLM can rewrite the query, read tables it should not see, or drop data.
SQL injection is the query-language cousin of command injection: user- or model-supplied text is concatenated or f-stringed into SQL text, and the driver dutifully parses whatever the attacker wrote. The canonical fix — parameterised queries — has been known since the 1990s, and yet SQL-injection bugs keep shipping because f-strings and template strings make the vulnerable path the easy one.
MCP servers that wrap a database as a tool ("query my analytics warehouse", "fetch my CRM contact") are one of the most common server shapes. The model's argument goes straight into a query the author planned. A document the model retrieved elsewhere can ask it to run `'; DROP TABLE users; --` — the model will forward that string exactly because that is what the tool told it to do.
@server.tool() |
def get_user(email: str) -> dict: |
# f-string interpolation — the model controls `email` |
cur.execute(f"SELECT * FROM users WHERE email = '{email}'") |
return cur.fetchone() |
@server.tool() |
def get_user(email: str) -> dict: |
# Parameterised — driver escapes the value, never the query shape |
cur.execute("SELECT * FROM users WHERE email = %s", (email,)) |
return cur.fetchone() |
We flag `cursor.execute`, `db.query`, `session.execute`, Knex raw, and Sequelize raw calls whose SQL argument is an f-string, template literal, or `+` concatenation. Parameterised calls with a tuple/list of bind values are the safe pattern and are not flagged.
See the full threat catalog for every documented detection.
CVEs of the same CWE class. Not MCP-specific, but exemplify the failure mode MCPSafe detects.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now