Server Implementation
Tool error paths return stack traces, SQL fragments, file paths, or full exception messages to the caller — information an attacker uses to refine the next payload.
A tool that does `except Exception as e: return str(e)` hands the attacker the server's internal state on a silver platter: absolute filesystem paths, DB driver error messages that echo the offending SQL, un-stripped stack traces, environment-variable names. The information leak turns a failed probe into a successful reconnaissance.
MCP tool errors propagate back to the model, which may summarise or quote them in follow-up reasoning. Even if the end-user never sees the raw error, the LLM has — and indirect prompt injection can instruct the model to echo the error text to an outbound channel. "Helpful" debugging details become exfiltrated reconnaissance.
@server.tool() |
def run_query(sql: str) -> str: |
try: |
return db.execute(sql).fetchall() |
except Exception as e: |
return f"query failed: {e}" # leaks SQL driver error verbatim |
import logging |
log = logging.getLogger(__name__) |
@server.tool() |
def run_query(sql: str) -> str: |
try: |
return db.execute(sql).fetchall() |
except Exception: |
log.exception("run_query_failed") # full detail to server logs only |
raise RuntimeError("query failed") # generic to caller |
We flag tool-handler `return` / JSON-response paths that include `str(e)`, `traceback.format_exc()`, `err.stack`, `err.message`, or template-interpolate an exception variable. Logging the exception server-side is the safe pattern and is not flagged.
See the full threat catalog for every documented detection.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now