Configuration & Environment
A tool itself calls a paid LLM API (OpenAI, Anthropic, Cohere, …) and passes through user-controlled input with no input cap and no `max_tokens` — a single adversarial call can cost tens of dollars. Paid-LLM variant of the unbounded-cost family; compute/memory is MCP-110, MCP sampling is MCP-211, oversized tool descriptions are MCP-252.
Denial-of-wallet is a DoS variant whose cost shows up on a credit-card statement instead of a pager. MCP servers that wrap an LLM — summarisers, translators, embedders — proxy paid API calls on the user's behalf. Without a cap on prompt length or output length, a single `summarise('A' * 500_000)` produces a six-figure token bill. Over a day, a loop driven by indirect prompt injection can drain a budget without triggering traditional rate-limits.
MCP encourages composition: a tool that calls an LLM is often called by another LLM. The outer model has no notion of cost; it will happily retry, expand, or loop. A missing `max_tokens` on the inner call is the difference between a healthy tool and a wallet-emptying proxy.
from openai import OpenAI |
client = OpenAI() |
@server.tool() |
def summarize(text: str) -> str: |
# No max_tokens, no input length check |
resp = client.chat.completions.create( |
model="gpt-4o", |
messages=[{"role": "user", "content": f"Summarize: {text}"}], |
) |
return resp.choices[0].message.content |
from openai import OpenAI |
client = OpenAI() |
_MAX_INPUT = 20_000 # characters |
@server.tool() |
def summarize(text: str) -> str: |
if len(text) > _MAX_INPUT: |
raise ValueError("input too long") |
resp = client.chat.completions.create( |
model="gpt-4o", |
messages=[{"role": "user", "content": f"Summarize: {text}"}], |
max_tokens=400, |
) |
return resp.choices[0].message.content |
We flag LLM SDK calls (`openai.*.create`, `anthropic.messages.create`, `cohere.chat`, `genai.*.generate_content`, Bedrock `converse`) inside `@server.tool` / `server.tool(...)` handlers when `max_tokens` / `max_output_tokens` is absent AND the prompt is built from a handler parameter with no obvious length check.
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