Use with caution. Address findings before production.
Scanned 5/24/2026, 7:34:16 PMยทCached resultยทDeep Scanยท91 rulesยทHow we decide โ
AIVSS Score
Medium
Severity Breakdown
0
critical
22
high
52
medium
5
low
MCP Server Information
Findings
This package has a concerning security grade of C and a low safety score of 52/100, with 22 high-severity issuesโprimarily around server misconfigurations, ANSI escape injection risks, and prompt injection vulnerabilities. While no critical flaws were found, the volume of high and medium findings (including command injection and resource exhaustion risks) suggests it may expose your system to unexpected behavior or exploitation if installed. Proceed with caution and review the specific misconfigurations before deployment.
No known CVEs found for this package or its dependencies.
DNS Rebinding Protection Disabled by Default in Model Context Protocol Python SDK for Servers Running on Localhost
MCP SDK FastMCP Server Validation Error Leading to Denial of Service
Unhandled Exception in Streamable HTTP Transport Leading to Denial of Service
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
Showing 1โ30 of 79 findings
79 findings
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 45 | # Try both npx.cmd and npx.exe on Windows |
| 46 | for cmd in ["npx.cmd", "npx.exe", "npx"]: |
| 47 | try: |
| 48 | subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True) |
| 49 | return cmd |
| 50 | except subprocess.CalledProcessError: |
| 51 | continue |
Remediation
Replace shell-based execution with list-arg subprocess calls (shell=False) or escape every interpolated value with shlex.quote (Python) / shell-escape (Node). Treat every MCP tool input as hostile.
File registers an MCP resource handler (`@mcp.resource`, `server.registerResource`, or a `"resources/read"` JSON-RPC handler) AND opens a file with no path-canonicalisation guard. The URI parameter from the client flows directly into a filesystem sink, letting a malicious request escape the intended root via `..` / absolute paths / symlinks. Canonicalise via `os.path.realpath` + `os.path.commonpath` / `path.resolve` + prefix check, OR use `secure_filename` / `pathlib.Path.relative_to(allowed_ro
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Replace this README with README.v2.md when v2 is released --> |
| 17 | |
| 18 | > [!NOTE] |
| 19 | > **This README documents v1.x of the MCP Pyt |
Remediation
Canonicalise the path with `os.path.realpath` + `os.path.commonpath`, or `path.resolve` + a prefix check against the allowed root; alternatively `pathlib.Path(uri).resolve().relative_to(allowed_root)` and reject on the ValueError. For TS/JS, use `path.resolve(root, sub)` then verify `resolved.startsWith(root + path.sep)`. Never read the URI directly into `open()` / `fs.readFile`.
File registers an MCP resource handler (`@mcp.resource`, `server.registerResource`, or a `"resources/read"` JSON-RPC handler) AND opens a file with no path-canonicalisation guard. The URI parameter from the client flows directly into a filesystem sink, letting a malicious request escape the intended root via `..` / absolute paths / symlinks. Canonicalise via `os.path.realpath` + `os.path.commonpath` / `path.resolve` + prefix check, OR use `secure_filename` / `pathlib.Path.relative_to(allowed_ro
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Move this content back to README.md when v2 is released --> |
| 17 | |
| 18 | > [!IMPORTANT] |
| 19 | > **This documents v2 of the SDK (currentl |
Remediation
Canonicalise the path with `os.path.realpath` + `os.path.commonpath`, or `path.resolve` + a prefix check against the allowed root; alternatively `pathlib.Path(uri).resolve().relative_to(allowed_root)` and reject on the ValueError. For TS/JS, use `path.resolve(root, sub)` then verify `resolved.startsWith(root + path.sep)`. Never read the URI directly into `open()` / `fs.readFile`.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | from mcp.server.mcpserver import MCPServer |
| 2 | from mcp.server.mcpserver.prompts import base |
| 3 | |
| 4 | mcp = MCPServer(name="Prompt Example") |
| 5 | |
| 6 | |
| 7 | @mcp.prompt(title="Code Review") |
| 8 | def review_code(code: str) -> str: |
| 9 | return f"Please review this code:\n\n{code}" |
| 10 | |
| 11 | |
| 12 | @mcp.prompt(title="Debug Assistant") |
| 13 | def debug_error(error: str) -> list[base.Message]: |
| 14 | return [ |
| 15 | base.UserMessage("I'm seeing this error:"), |
| 16 | base.UserMessage(error), |
| 17 | base.AssistantMessage("I'll help debug that. What have y |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | """MCPServer Echo Server""" |
| 2 | |
| 3 | from mcp.server.mcpserver import MCPServer |
| 4 | |
| 5 | # Create server |
| 6 | mcp = MCPServer("Echo Server") |
| 7 | |
| 8 | |
| 9 | @mcp.tool() |
| 10 | def echo_tool(text: str) -> str: |
| 11 | """Echo the input text""" |
| 12 | return text |
| 13 | |
| 14 | |
| 15 | @mcp.resource("echo://static") |
| 16 | def echo_resource() -> str: |
| 17 | return "Echo!" |
| 18 | |
| 19 | |
| 20 | @mcp.resource("echo://{text}") |
| 21 | def echo_template(text: str) -> str: |
| 22 | """Echo the input text""" |
| 23 | return f"Echo: {text}" |
| 24 | |
| 25 | |
| 26 | @mcp.prompt("echo") |
| 27 | def echo_prompt(text: str) -> str: |
| 28 | return text |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
LLM consensus
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Replace this README with README.v2.md when v2 is released --> |
| 17 | |
| 18 | > [!NOTE] |
| 19 | > **This README documents v1.x of the MCP Pyt |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | """MCPServer quickstart example. |
| 2 | |
| 3 | Run from the repository root: |
| 4 | uv run examples/snippets/servers/mcpserver_quickstart.py |
| 5 | """ |
| 6 | |
| 7 | from mcp.server.mcpserver import MCPServer |
| 8 | |
| 9 | # Create an MCP server |
| 10 | mcp = MCPServer("Demo") |
| 11 | |
| 12 | |
| 13 | # Add an addition tool |
| 14 | @mcp.tool() |
| 15 | def add(a: int, b: int) -> int: |
| 16 | """Add two numbers""" |
| 17 | return a + b |
| 18 | |
| 19 | |
| 20 | # Add a dynamic greeting resource |
| 21 | @mcp.resource("greeting://{name}") |
| 22 | def get_greeting(name: str) -> str: |
| 23 | """Get a personalized greeting""" |
| 24 | return f"Hello, {n |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | from mcp.server.mcpserver import MCPServer |
| 2 | from mcp.types import ( |
| 3 | Completion, |
| 4 | CompletionArgument, |
| 5 | CompletionContext, |
| 6 | PromptReference, |
| 7 | ResourceTemplateReference, |
| 8 | ) |
| 9 | |
| 10 | mcp = MCPServer(name="Example") |
| 11 | |
| 12 | |
| 13 | @mcp.resource("github://repos/{owner}/{repo}") |
| 14 | def github_repo(owner: str, repo: str) -> str: |
| 15 | """GitHub repository resource.""" |
| 16 | return f"Repository: {owner}/{repo}" |
| 17 | |
| 18 | |
| 19 | @mcp.prompt(description="Code review prompt") |
| 20 | def review_code(language: str, code: str) -> str: |
| 21 | """Gen |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | !!! info "You are viewing the in-development v2 documentation" |
| 4 | For the current stable release, see the [v1.x documentation](https://py.sdk.modelcontextprotocol.io/). |
| 5 | |
| 6 | The **Model Context Protocol (MCP)** allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. |
| 7 | |
| 8 | This Python SDK implements the full MCP specification, making it easy to: |
| 9 | |
| 10 | - **Build MCP servers** that expose resources, pr |
Remediation
Wrap the parameter in `<untrusted>...</untrusted>` so the LLM treats it as data, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `JSON.stringify`. If you control the prompt template, prefer keeping it static and putting user data in a separate `role: "user"` message.
File mounts an HTTP route that handles MCP `tools/list` (Express / Fastify / FastAPI / Flask) but the route โ and the router it sits behind โ has no auth middleware applied. An anonymous client can enumerate every tool the server exposes, scope the attack surface, and (if `tools/call` shares the route) invoke them. Apply auth at the route or router level: Express `passport.authenticate(...)` / a `requireAuth`-style middleware, FastAPI `Depends(get_current_user)` or `Depends(verify_jwt)`, Flask
Evidence
| 1 | from __future__ import annotations |
| 2 | |
| 3 | from datetime import datetime |
| 4 | from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar |
| 5 | |
| 6 | from pydantic import BaseModel, ConfigDict, Field, FileUrl, TypeAdapter |
| 7 | from pydantic.alias_generators import to_camel |
| 8 | from typing_extensions import NotRequired, TypedDict |
| 9 | |
| 10 | from mcp.types.jsonrpc import RequestId |
| 11 | |
| 12 | LATEST_PROTOCOL_VERSION = "2025-11-25" |
| 13 | """The latest version of the Model Context Protocol. |
| 14 | |
| 15 | You can find the latest specification at https: |
Remediation
Apply auth middleware at the route or router level: - Express / Fastify / Koa: `passport.authenticate(...)`, `requireAuth`, `verifyToken`, or an equivalent JWT middleware applied via `router.use(authMw)` or as a per-route handler. - FastAPI: `Depends(get_current_user)`, `OAuth2PasswordBearer`, `HTTPBearer`, or `verify_jwt` dependency. - Flask: `@login_required`, `@auth_required`, `@jwt_required`, or call `verify_jwt_in_request()` in the handler. Mounting MCP behind a s
File mounts an HTTP route that handles MCP `tools/list` (Express / Fastify / FastAPI / Flask) but the route โ and the router it sits behind โ has no auth middleware applied. An anonymous client can enumerate every tool the server exposes, scope the attack surface, and (if `tools/call` shares the route) invoke them. Apply auth at the route or router level: Express `passport.authenticate(...)` / a `requireAuth`-style middleware, FastAPI `Depends(get_current_user)` or `Depends(verify_jwt)`, Flask
Evidence
| 1 | """MCP Server Module |
| 2 | |
| 3 | This module provides a framework for creating an MCP (Model Context Protocol) server. |
| 4 | It allows you to easily define and handle various types of requests and notifications |
| 5 | using constructor-based handler registration. |
| 6 | |
| 7 | Usage: |
| 8 | 1. Define handler functions: |
| 9 | async def my_list_tools(ctx, params): |
| 10 | return types.ListToolsResult(tools=[...]) |
| 11 | |
| 12 | async def my_call_tool(ctx, params): |
| 13 | return types.CallToolResult(content=[...]) |
| 14 | |
| 15 | 2. Create a Server instance with on_* han |
Remediation
Apply auth middleware at the route or router level: - Express / Fastify / Koa: `passport.authenticate(...)`, `requireAuth`, `verifyToken`, or an equivalent JWT middleware applied via `router.use(authMw)` or as a per-route handler. - FastAPI: `Depends(get_current_user)`, `OAuth2PasswordBearer`, `HTTPBearer`, or `verify_jwt` dependency. - Flask: `@login_required`, `@auth_required`, `@jwt_required`, or call `verify_jwt_in_request()` in the handler. Mounting MCP behind a s
MCP server binds an HTTP transport to localhost and registers tools, but no authentication is enforced on requests. The official MCP security best practices warn that this is reachable via DNS-rebinding attacks โ a malicious web page can hit `http://127.0.0.1:<port>` from inside the user's browser and invoke tools as the user. Pick one fix: 1. Switch to stdio transport (`mcp.run(transport="stdio")`). 2. Require an `Authorization` / `Bearer` / `api_key` check on every request. 3. Bind
Evidence
| 1 | """Run from the repository root: |
| 2 | uv run examples/snippets/servers/streamable_config.py |
| 3 | """ |
| 4 | |
| 5 | from mcp.server.mcpserver import MCPServer |
| 6 | |
| 7 | mcp = MCPServer("StatelessServer") |
| 8 | |
| 9 | |
| 10 | # Add a simple tool to demonstrate the server |
| 11 | @mcp.tool() |
| 12 | def greet(name: str = "World") -> str: |
| 13 | """Greet someone by name.""" |
| 14 | return f"Hello, {name}!" |
| 15 | |
| 16 | |
| 17 | # Run server with streamable_http transport |
| 18 | # Transport-specific options (stateless_http, json_response) are passed to run() |
| 19 | if __name__ == "__main__": |
| 20 | # Stateles |
Remediation
Pick one: 1. Switch to stdio transport (preferred โ `mcp.run(transport="stdio")`). 2. Require an `Authorization: Bearer <token>` check on every request. Token MUST be unguessable; rotate per-launch. 3. Bind to a Unix domain socket (`path="/var/run/mcp.sock"`) with file-system permissions instead of TCP. Localhost-only TCP without auth is NOT safe โ DNS-rebinding lets a malicious web page reach `http://127.0.0.1:<port>` from inside the browser.
MCP server binds an HTTP transport to localhost and registers tools, but no authentication is enforced on requests. The official MCP security best practices warn that this is reachable via DNS-rebinding attacks โ a malicious web page can hit `http://127.0.0.1:<port>` from inside the user's browser and invoke tools as the user. Pick one fix: 1. Switch to stdio transport (`mcp.run(transport="stdio")`). 2. Require an `Authorization` / `Bearer` / `api_key` check on every request. 3. Bind
Evidence
| 1 | """MCPServer quickstart example. |
| 2 | |
| 3 | Run from the repository root: |
| 4 | uv run examples/snippets/servers/mcpserver_quickstart.py |
| 5 | """ |
| 6 | |
| 7 | from mcp.server.mcpserver import MCPServer |
| 8 | |
| 9 | # Create an MCP server |
| 10 | mcp = MCPServer("Demo") |
| 11 | |
| 12 | |
| 13 | # Add an addition tool |
| 14 | @mcp.tool() |
| 15 | def add(a: int, b: int) -> int: |
| 16 | """Add two numbers""" |
| 17 | return a + b |
| 18 | |
| 19 | |
| 20 | # Add a dynamic greeting resource |
| 21 | @mcp.resource("greeting://{name}") |
| 22 | def get_greeting(name: str) -> str: |
| 23 | """Get a personalized greeting""" |
| 24 | return f"Hello, {n |
Remediation
Pick one: 1. Switch to stdio transport (preferred โ `mcp.run(transport="stdio")`). 2. Require an `Authorization: Bearer <token>` check on every request. Token MUST be unguessable; rotate per-launch. 3. Bind to a Unix domain socket (`path="/var/run/mcp.sock"`) with file-system permissions instead of TCP. Localhost-only TCP without auth is NOT safe โ DNS-rebinding lets a malicious web page reach `http://127.0.0.1:<port>` from inside the browser.
MCP server binds an HTTP transport to localhost and registers tools, but no authentication is enforced on requests. The official MCP security best practices warn that this is reachable via DNS-rebinding attacks โ a malicious web page can hit `http://127.0.0.1:<port>` from inside the user's browser and invoke tools as the user. Pick one fix: 1. Switch to stdio transport (`mcp.run(transport="stdio")`). 2. Require an `Authorization` / `Bearer` / `api_key` check on every request. 3. Bind
Evidence
| 1 | #!/usr/bin/env python3 |
| 2 | """MCP Everything Server - Conformance Test Server |
| 3 | |
| 4 | Server implementing all MCP features for conformance testing based on Conformance Server Specification. |
| 5 | """ |
| 6 | |
| 7 | import asyncio |
| 8 | import base64 |
| 9 | import json |
| 10 | import logging |
| 11 | |
| 12 | import click |
| 13 | from mcp.server import ServerRequestContext |
| 14 | from mcp.server.mcpserver import Context, MCPServer |
| 15 | from mcp.server.mcpserver.prompts.base import UserMessage |
| 16 | from mcp.server.streamable_http import EventCallback, EventMessage, EventStore |
| 17 | from mcp.type |
Remediation
Pick one: 1. Switch to stdio transport (preferred โ `mcp.run(transport="stdio")`). 2. Require an `Authorization: Bearer <token>` check on every request. Token MUST be unguessable; rotate per-launch. 3. Bind to a Unix domain socket (`path="/var/run/mcp.sock"`) with file-system permissions instead of TCP. Localhost-only TCP without auth is NOT safe โ DNS-rebinding lets a malicious web page reach `http://127.0.0.1:<port>` from inside the browser.
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | """Run from the repository root: |
| 2 | uv run examples/snippets/servers/oauth_server.py |
| 3 | """ |
| 4 | |
| 5 | from pydantic import AnyHttpUrl |
| 6 | |
| 7 | from mcp.server.auth.provider import AccessToken, TokenVerifier |
| 8 | from mcp.server.auth.settings import AuthSettings |
| 9 | from mcp.server.mcpserver import MCPServer |
| 10 | |
| 11 | |
| 12 | class SimpleTokenVerifier(TokenVerifier): |
| 13 | """Simple token verifier for demonstration.""" |
| 14 | |
| 15 | async def verify_token(self, token: str) -> AccessToken | None: |
| 16 | pass # This is where you would implement actual to |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
LLM consensus
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Move this content back to README.md when v2 is released --> |
| 17 | |
| 18 | > [!IMPORTANT] |
| 19 | > **This documents v2 of the SDK (currentl |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Replace this README with README.v2.md when v2 is released --> |
| 17 | |
| 18 | > [!NOTE] |
| 19 | > **This README documents v1.x of the MCP Pyt |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | """MCPServer quickstart example. |
| 2 | |
| 3 | Run from the repository root: |
| 4 | uv run examples/snippets/servers/mcpserver_quickstart.py |
| 5 | """ |
| 6 | |
| 7 | from mcp.server.mcpserver import MCPServer |
| 8 | |
| 9 | # Create an MCP server |
| 10 | mcp = MCPServer("Demo") |
| 11 | |
| 12 | |
| 13 | # Add an addition tool |
| 14 | @mcp.tool() |
| 15 | def add(a: int, b: int) -> int: |
| 16 | """Add two numbers""" |
| 17 | return a + b |
| 18 | |
| 19 | |
| 20 | # Add a dynamic greeting resource |
| 21 | @mcp.resource("greeting://{name}") |
| 22 | def get_greeting(name: str) -> str: |
| 23 | """Get a personalized greeting""" |
| 24 | return f"Hello, {n |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | """Run from the repository root: |
| 2 | uv run examples/snippets/servers/streamable_config.py |
| 3 | """ |
| 4 | |
| 5 | from mcp.server.mcpserver import MCPServer |
| 6 | |
| 7 | mcp = MCPServer("StatelessServer") |
| 8 | |
| 9 | |
| 10 | # Add a simple tool to demonstrate the server |
| 11 | @mcp.tool() |
| 12 | def greet(name: str = "World") -> str: |
| 13 | """Greet someone by name.""" |
| 14 | return f"Hello, {name}!" |
| 15 | |
| 16 | |
| 17 | # Run server with streamable_http transport |
| 18 | # Transport-specific options (stateless_http, json_response) are passed to run() |
| 19 | if __name__ == "__main__": |
| 20 | # Stateles |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | #!/usr/bin/env python3 |
| 2 | """MCP Everything Server - Conformance Test Server |
| 3 | |
| 4 | Server implementing all MCP features for conformance testing based on Conformance Server Specification. |
| 5 | """ |
| 6 | |
| 7 | import asyncio |
| 8 | import base64 |
| 9 | import json |
| 10 | import logging |
| 11 | |
| 12 | import click |
| 13 | from mcp.server import ServerRequestContext |
| 14 | from mcp.server.mcpserver import Context, MCPServer |
| 15 | from mcp.server.mcpserver.prompts.base import UserMessage |
| 16 | from mcp.server.streamable_http import EventCallback, EventMessage, EventStore |
| 17 | from mcp.type |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | # Migration Guide: v1 to v2 |
| 2 | |
| 3 | This guide covers the breaking changes introduced in v2 of the MCP Python SDK and how to update your code. |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | Version 2 of the MCP Python SDK introduces several breaking changes to improve the API, align with the MCP specification, and provide better type safety. |
| 8 | |
| 9 | ## Breaking Changes |
| 10 | |
| 11 | ### `streamablehttp_client` removed |
| 12 | |
| 13 | The deprecated `streamablehttp_client` function has been removed. Use `streamable_http_client` instead. |
| 14 | |
| 15 | **Before (v1):** |
| 16 | |
| 17 | ```python |
| 18 | from |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding โ a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK โฅ1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | !!! info "You are viewing the in-development v2 documentation" |
| 4 | For the current stable release, see the [v1.x documentation](https://py.sdk.modelcontextprotocol.io/). |
| 5 | |
| 6 | The **Model Context Protocol (MCP)** allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. |
| 7 | |
| 8 | This Python SDK implements the full MCP specification, making it easy to: |
| 9 | |
| 10 | - **Build MCP servers** that expose resources, pr |
Remediation
Pick one: 1. Upgrade to `@modelcontextprotocol/sdk` โฅ 1.24.0 and use `createMcpExpressApp()` โ Host validation is on by default. 2. Wire `hostHeaderValidation({ allowedHosts: [...] })` middleware into your custom Express setup. 3. Add a request-scoped Host check: `if (req.headers.host !== "127.0.0.1:<port>") return res.status(403).end();` 4. Add FastAPI `TrustedHostMiddleware(allowed_hosts=["127.0.0.1"])` (Python). Even with `Authorization: Bearer` checks, a missing
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 370 | # Or for SSE |
| 371 | mcp = MCPServer("Server") |
| 372 | mcp.run(transport="sse", host="0.0.0.0", port=9000, sse_path="/events") |
| 373 | ``` |
| 374 | |
| 375 | **For mounted apps:** |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 355 | mcp.run(transport="streamable-http") |
| 356 | |
| 357 | # Or for SSE |
| 358 | mcp = FastMCP("Server", host="0.0.0.0", port=9000, sse_path="/events") |
| 359 | mcp.run(transport="sse") |
| 360 | ``` |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 74 | message = params.message |
| 75 | |
| 76 | if not url: |
| 77 | print("Error: No URL provided in elicitation request") |
| 78 | return types.ElicitResult(action="cancel") |
| 79 | |
| 80 | # Reject dangerous URL schemes before prompting the user |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 896 | async def handle_progress(ctx: ServerRequestContext, params: ProgressNotificationParams) -> None: |
| 897 | print(f"Progress: {params.progress}/{params.total}") |
| 898 | |
| 899 | server = Server("my-server", on_progress=handle_progress) |
| 900 | ``` |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 100 | async def handle_elicitation(context, params: ElicitRequestParams) -> ElicitResult: |
| 101 | # Display the message to the user |
| 102 | print(f"Server asks: {params.message}") |
| 103 | |
| 104 | # Collect user input (this is a simplified example) |
| 105 | response = input("Your response (y/n): ") |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 2115 | async def handle_sampling_message( |
| 2116 | context: ClientRequestContext, params: types.CreateMessageRequestParams |
| 2117 | ) -> types.CreateMessageResult: |
| 2118 | print(f"Sampling request: {params.messages}") |
| 2119 | return types.CreateMessageResult( |
| 2120 | role="assistant", |
| 2121 | content=types.TextContent( |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 2175 | async def handle_sampling_message( |
| 2176 | context: RequestContext[ClientSession, None], params: types.CreateMessageRequestParams |
| 2177 | ) -> types.CreateMessageResult: |
| 2178 | print(f"Sampling request: {params.messages}") |
| 2179 | return types.CreateMessageResult( |
| 2180 | role="assistant", |
| 2181 | content=types.TextContent( |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 21 | async def handle_sampling_message( |
| 22 | context: ClientRequestContext, params: types.CreateMessageRequestParams |
| 23 | ) -> types.CreateMessageResult: |
| 24 | print(f"Sampling request: {params.messages}") |
| 25 | return types.CreateMessageResult( |
| 26 | role="assistant", |
| 27 | content=types.TextContent( |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 287 | async def elicitation_callback(context, params: ElicitRequestParams) -> ElicitResult: |
| 288 | print(f"\n[Elicitation] {params.message}") |
| 289 | response = input("Confirm? (y/n): ") |
| 290 | return ElicitResult(action="accept", content={"confirm": response.lower() == "y"}) |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 342 | def main() -> None: |
| 343 | """Main entry point for the conformance client.""" |
| 344 | if len(sys.argv) < 2: |
| 345 | print(f"Usage: {sys.argv[0]} <server-url>", file=sys.stderr) |
| 346 | sys.exit(1) |
| 347 | |
| 348 | server_url = sys.argv[1] |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 27 | params: ElicitRequestParams, |
| 28 | ) -> ElicitResult: |
| 29 | """Handle elicitation requests from the server.""" |
| 30 | print(f"\n[Elicitation] Server asks: {params.message}") |
| 31 | |
| 32 | # Simple terminal prompt |
| 33 | response = input("Your response (y/n): ").strip().lower() |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 243 | return f"Elicitation result: action={result.action}, content={content}" |
| 244 | except Exception as e: |
| 245 | return f"Elicitation not supported or error: {str(e)}" |
| 246 | |
| 247 | |
| 248 | class EnumSchemasTestSchema(BaseModel): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 189 | return f"LLM response: {model_response}" |
| 190 | except Exception as e: |
| 191 | return f"Sampling not supported or error: {str(e)}" |
| 192 | |
| 193 | |
| 194 | class UserResponse(BaseModel): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 211 | return f"User response: action={result.action}, content={content}" |
| 212 | except Exception as e: |
| 213 | return f"Elicitation not supported or error: {str(e)}" |
| 214 | |
| 215 | |
| 216 | class SEP1034DefaultsSchema(BaseModel): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 302 | return f"Elicitation completed: action={result.action}, content={content}" |
| 303 | except Exception as e: |
| 304 | return f"Elicitation not supported or error: {str(e)}" |
| 305 | |
| 306 | |
| 307 | @mcp.tool() |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 313 | except MCPError: |
| 314 | raise |
| 315 | except Exception as e: |
| 316 | return CallToolResult(content=[TextContent(type="text", text=str(e))], is_error=True) |
| 317 | if isinstance(result, CallToolResult): |
| 318 | return result |
| 319 | if isinstance(result, tuple) and len(result) == 2: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 45 | # Try both npx.cmd and npx.exe on Windows |
| 46 | for cmd in ["npx.cmd", "npx.exe", "npx"]: |
| 47 | try: |
| 48 | subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True) |
| 49 | return cmd |
| 50 | except subprocess.CalledProcessError: |
| 51 | continue |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 206 | async def update_importance(user_embedding: list[float], deps: Deps): |
| 207 | async with deps.pool.acquire() as conn: |
| 208 | rows = await conn.fetch("SELECT id, importance, access_count, embedding FROM memories") |
| 209 | for row in rows: |
| 210 | memory_embedding = row["embedding"] |
| 211 | similarity = cosine_similarity(user_embedding, memory_embedding) |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
LLM consensus
MCP tool file registers a tool AND contains a destructive sink (fs.unlink, os.remove, shutil.rmtree, DROP/DELETE FROM/TRUNCATE/ UPDATE ... SET, HTTP DELETE/PUT/PATCH, subprocess.run, exec/spawn), but the file has no confirmation path โ no `elicit()` call, no `dry_run`/`dryRun` flag, no `idempotency_key`/`idempotencyKey`, no `confirmation`/`confirm` token. A destructive handler that fires on the first call with no reversibility signal is an autonomous-action hazard (MCP Top-10 R10). Add one of:
Evidence
| 1 | # /// script |
| 2 | # dependencies = ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector"] |
| 3 | # /// |
| 4 | |
| 5 | # uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector |
| 6 | |
| 7 | """Recursive memory system inspired by the human brain's clustering of memories. |
| 8 | Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient |
| 9 | similarity search. |
| 10 | """ |
| 11 | |
| 12 | import asyncio |
| 13 | import math |
| 14 | import os |
| 15 | from dataclasses import dataclass |
| 16 | from datetime import datetime, timezone |
| 17 | from pathlib import Path |
| 18 | from typing import An |
Remediation
Pick one confirmation mechanism and add it to the destructive handler: - make the client confirm via `elicit()` before touching the sink, - accept a `dry_run` / `dryRun` boolean input and branch off the sink when true (return what would change), - require an `idempotency_key` / `idempotencyKey` the caller provides on the first call and echoes back to commit, - require a `confirmation` / `confirm` token as an input field. If the destructive call is genuinely irrelevant to the tool
LLM consensus
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 1722 | description="Query the database", |
| 1723 | inputSchema={ |
| 1724 | "type": "object", |
| 1725 | "properties": {"query": {"type": "string", "description": "SQL query to execute"}}, |
| 1726 | "required": ["query"], |
| 1727 | }, |
| 1728 | ) |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 29 | # see OAuthClientMetadata; we only support `code` |
| 30 | response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow") |
| 31 | code_challenge: str = Field(..., description="PKCE code challenge") |
| 32 | code_challenge_method: Literal["S256"] = Field("S256", description="PKCE code challenge method, must be S256") |
| 33 | state: str | None = Field(None, description="Optional state parameter") |
| 34 | scope: str | None = Field( |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 33 | """Parameters for initializing an sse_client.""" |
| 34 | |
| 35 | # The endpoint URL. |
| 36 | url: str |
| 37 | |
| 38 | # Optional headers to include in requests. |
| 39 | headers: dict[str, Any] | None = None |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 49 | """Parameters for initializing a streamable_http_client.""" |
| 50 | |
| 51 | # The endpoint URL. |
| 52 | url: str |
| 53 | |
| 54 | # Optional headers to include in requests. |
| 55 | headers: dict[str, Any] | None = None |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 1689 | description="Query the database", |
| 1690 | input_schema={ |
| 1691 | "type": "object", |
| 1692 | "properties": {"query": {"type": "string", "description": "SQL query to execute"}}, |
| 1693 | "required": ["query"], |
| 1694 | }, |
| 1695 | ) |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 28 | "type": "object", |
| 29 | "required": ["url"], |
| 30 | "properties": { |
| 31 | "url": { |
| 32 | "type": "string", |
| 33 | "description": "URL to fetch", |
| 34 | } |
| 35 | }, |
| 36 | }, |
| 37 | ) |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 153 | class HttpResource(Resource): |
| 154 | """A resource that reads from an HTTP endpoint.""" |
| 155 | |
| 156 | url: str = Field(description="URL to fetch content from") |
| 157 | mime_type: str = Field(default="application/json", description="MIME type of the resource content") |
| 158 | |
| 159 | async def read(self) -> str | bytes: |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 10 | class AuthorizationParams(BaseModel): |
| 11 | state: str | None |
| 12 | scopes: list[str] | None |
| 13 | code_challenge: str |
| 14 | redirect_uri: AnyUrl |
| 15 | redirect_uri_provided_explicitly: bool |
| 16 | resource: str | None = None # RFC 8707 resource indicator |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 1662 | message: str |
| 1663 | """The message to present to the user explaining why the interaction is needed.""" |
| 1664 | |
| 1665 | url: str |
| 1666 | """The URL that the user should navigate to.""" |
| 1667 | |
| 1668 | elicitation_id: str |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 17 | class AuthorizationCode(BaseModel): |
| 18 | code: str |
| 19 | scopes: list[str] |
| 20 | expires_at: float |
| 21 | client_id: str |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 21 | scopes: list[str] |
| 22 | expires_at: float |
| 23 | client_id: str |
| 24 | code_challenge: str |
| 25 | redirect_uri: AnyUrl |
| 26 | redirect_uri_provided_explicitly: bool |
| 27 | resource: str | None = None # RFC 8707 resource indicator |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 23 | # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 |
| 24 | client_secret: str | None = None |
| 25 | # See https://datatracker.ietf.org/doc/html/rfc7636#section-4.5 |
| 26 | code_verifier: str = Field(..., description="PKCE code verifier") |
| 27 | # RFC 8707 resource indicator |
| 28 | resource: str | None = Field(None, description="Resource indicator for the token") |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 69 | class StdioServerParameters(BaseModel): |
| 70 | command: str |
| 71 | """The executable to run to start the server.""" |
| 72 | |
| 73 | args: list[str] = Field(default_factory=list) |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 17 | class AuthorizationCodeRequest(BaseModel): |
| 18 | # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3 |
| 19 | grant_type: Literal["authorization_code"] |
| 20 | code: str = Field(..., description="The authorization code") |
| 21 | redirect_uri: AnyUrl | None = Field(None, description="Must be the same as redirect URI provided in /authorize") |
| 22 | client_id: str |
| 23 | # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 56 | description="Query the database", |
| 57 | input_schema={ |
| 58 | "type": "object", |
| 59 | "properties": {"query": {"type": "string", "description": "SQL query to execute"}}, |
| 60 | "required": ["query"], |
| 61 | }, |
| 62 | ) |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "mcpServers": { |
| 3 | "sqlite": { |
| 4 | "command": "uvx", |
| 5 | "args": ["mcp-server-sqlite", "--db-path", "./test.db"] |
| 6 | }, |
| 7 | "puppeteer": { |
| 8 | "command": "npx", |
| 9 | "args": ["-y", "@modelcontextprotocol/server-puppeteer"] |
| 10 | } |
| 11 | } |
| 12 | } |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | """URL Elicitation Client Example. |
| 2 | |
| 3 | Demonstrates how clients handle URL elicitation requests from servers. |
| 4 | This is the Python equivalent of TypeScript SDK's elicitationUrlExample.ts, |
| 5 | focused on URL elicitation patterns without OAuth complexity. |
| 6 | |
| 7 | Features demonstrated: |
| 8 | 1. Client elicitation capability declaration |
| 9 | 2. Handling elicitation requests from servers via callback |
| 10 | 3. Catching UrlElicitationRequiredError from tool calls |
| 11 | 4. Browser interaction with security warnings |
| 12 | 5. Interactive CLI for te |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | from __future__ import annotations |
| 2 | |
| 3 | from datetime import datetime |
| 4 | from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar |
| 5 | |
| 6 | from pydantic import BaseModel, ConfigDict, Field, FileUrl, TypeAdapter |
| 7 | from pydantic.alias_generators import to_camel |
| 8 | from typing_extensions import NotRequired, TypedDict |
| 9 | |
| 10 | from mcp.types.jsonrpc import RequestId |
| 11 | |
| 12 | LATEST_PROTOCOL_VERSION = "2025-11-25" |
| 13 | """The latest version of the Model Context Protocol. |
| 14 | |
| 15 | You can find the latest specification at https: |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | """Unified MCP Client that wraps ClientSession with transport management.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from contextlib import AsyncExitStack |
| 6 | from dataclasses import KW_ONLY, dataclass, field |
| 7 | from typing import Any |
| 8 | |
| 9 | from mcp.client._memory import InMemoryTransport |
| 10 | from mcp.client._transport import Transport |
| 11 | from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT |
| 12 | from mcp.client.streamable_http import streamable_http_client |
| 13 |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | #!/usr/bin/env python3 |
| 2 | """Simple MCP client example with OAuth authentication support. |
| 3 | |
| 4 | This client connects to an MCP server using streamable HTTP transport with OAuth. |
| 5 | |
| 6 | """ |
| 7 | |
| 8 | from __future__ import annotations as _annotations |
| 9 | |
| 10 | import asyncio |
| 11 | import os |
| 12 | import socketserver |
| 13 | import threading |
| 14 | import time |
| 15 | import webbrowser |
| 16 | from http.server import BaseHTTPRequestHandler, HTTPServer |
| 17 | from typing import Any |
| 18 | from urllib.parse import parse_qs, urlparse |
| 19 | |
| 20 | import httpx |
| 21 | from mcp.client._transport import ReadSt |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | """Experimental client-side task support. |
| 2 | |
| 3 | This module provides client methods for interacting with MCP tasks. |
| 4 | |
| 5 | WARNING: These APIs are experimental and may change without notice. |
| 6 | |
| 7 | Example: |
| 8 | ```python |
| 9 | # Call a tool as a task |
| 10 | result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"}) |
| 11 | task_id = result.task.task_id |
| 12 | |
| 13 | # Get task status |
| 14 | status = await session.experimental.get_task(task_id) |
| 15 | |
| 16 | # Get task result when complete |
| 17 | if status.status == "co |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | from __future__ import annotations |
| 2 | |
| 3 | from collections.abc import Callable |
| 4 | from typing import TYPE_CHECKING, Any |
| 5 | |
| 6 | from mcp.server.mcpserver.exceptions import ToolError |
| 7 | from mcp.server.mcpserver.tools.base import Tool |
| 8 | from mcp.server.mcpserver.utilities.logging import get_logger |
| 9 | from mcp.types import Icon, ToolAnnotations |
| 10 | |
| 11 | if TYPE_CHECKING: |
| 12 | from mcp.server.context import LifespanContextT, RequestT |
| 13 | from mcp.server.mcpserver.context import Context |
| 14 | |
| 15 | logger = get_logger(__name__) |
| 16 | |
| 17 | |
| 18 | class ToolMa |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | # Client Task Usage |
| 2 | |
| 3 | !!! warning "Experimental" |
| 4 | |
| 5 | Tasks are an experimental feature. The API may change without notice. |
| 6 | |
| 7 | This guide covers calling task-augmented tools from clients, handling the `input_required` status, and advanced patterns like receiving task requests from servers. |
| 8 | |
| 9 | ## Quick Start |
| 10 | |
| 11 | Call a tool as a task and poll for the result: |
| 12 | |
| 13 | ```python |
| 14 | from mcp.client.session import ClientSession |
| 15 | from mcp.types import CallToolResult |
| 16 | |
| 17 | async with ClientSession(read, write) as session: |
| 18 | |
Remediation
Tool descriptions should describe what the tool does โ not what the model should do with other tools. If a tool's correct operation legitimately requires another tool to be called, document that as a `composition` requirement in human-readable docs and let the calling code orchestrate, not the LLM. If the directive phrasing is coming from external content the tool retrieved (RAG, web fetch), wrap in `<untrusted>` tags and rely on the system prompt to flag tag-bound content as data, not instruct
MCP server registers tool handlers that call `elicit()` / `elicitInput()`, but no rate-limit middleware is wired into this file. The MCP elicitation spec SHOULD require clients AND servers to rate-limit elicitation calls โ without it, a compromised client can flood the user with prompts (denial- of-attention) or grind through enumeration attacks against multi-step elicitation flows. Add `slowapi` (Python), `express-rate-limit` (Node), or an equivalent per-session limiter on the elicitation hand
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Replace this README with README.v2.md when v2 is released --> |
| 17 | |
| 18 | > [!NOTE] |
| 19 | > **This README documents v1.x of the MCP Pyt |
Remediation
```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @mcp.tool() @limiter.limit("5/minute") async def confirm_action(ctx) -> str: return await ctx.elicit("Continue?", {"yes": "boolean"}) ```
MCP server registers tool handlers that call `elicit()` / `elicitInput()`, but no rate-limit middleware is wired into this file. The MCP elicitation spec SHOULD require clients AND servers to rate-limit elicitation calls โ without it, a compromised client can flood the user with prompts (denial- of-attention) or grind through enumeration attacks against multi-step elicitation flows. Add `slowapi` (Python), `express-rate-limit` (Node), or an equivalent per-session limiter on the elicitation hand
Evidence
| 1 | """Elicitation examples demonstrating form and URL mode elicitation. |
| 2 | |
| 3 | Form mode elicitation collects structured, non-sensitive data through a schema. |
| 4 | URL mode elicitation directs users to external URLs for sensitive operations |
| 5 | like OAuth flows, credential collection, or payment processing. |
| 6 | """ |
| 7 | |
| 8 | import uuid |
| 9 | |
| 10 | from pydantic import BaseModel, Field |
| 11 | |
| 12 | from mcp.server.mcpserver import Context, MCPServer |
| 13 | from mcp.shared.exceptions import UrlElicitationRequiredError |
| 14 | from mcp.types import ElicitRequestURL |
Remediation
```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @mcp.tool() @limiter.limit("5/minute") async def confirm_action(ctx) -> str: return await ctx.elicit("Continue?", {"yes": "boolean"}) ```
MCP server registers tool handlers that call `elicit()` / `elicitInput()`, but no rate-limit middleware is wired into this file. The MCP elicitation spec SHOULD require clients AND servers to rate-limit elicitation calls โ without it, a compromised client can flood the user with prompts (denial- of-attention) or grind through enumeration attacks against multi-step elicitation flows. Add `slowapi` (Python), `express-rate-limit` (Node), or an equivalent per-session limiter on the elicitation hand
Evidence
| 1 | # MCP Python SDK |
| 2 | |
| 3 | <div align="center"> |
| 4 | |
| 5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong> |
| 6 | |
| 7 | [![PyPI][pypi-badge]][pypi-url] |
| 8 | [![MIT licensed][mit-badge]][mit-url] |
| 9 | [![Python Version][python-badge]][python-url] |
| 10 | [![Documentation][docs-badge]][docs-url] |
| 11 | [![Protocol][protocol-badge]][protocol-url] |
| 12 | [![Specification][spec-badge]][spec-url] |
| 13 | |
| 14 | </div> |
| 15 | |
| 16 | <!-- TODO(v2): Move this content back to README.md when v2 is released --> |
| 17 | |
| 18 | > [!IMPORTANT] |
| 19 | > **This documents v2 of the SDK (currentl |
Remediation
```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @mcp.tool() @limiter.limit("5/minute") async def confirm_action(ctx) -> str: return await ctx.elicit("Continue?", {"yes": "boolean"}) ```
MCP server registers tool handlers that call `elicit()` / `elicitInput()`, but no rate-limit middleware is wired into this file. The MCP elicitation spec SHOULD require clients AND servers to rate-limit elicitation calls โ without it, a compromised client can flood the user with prompts (denial- of-attention) or grind through enumeration attacks against multi-step elicitation flows. Add `slowapi` (Python), `express-rate-limit` (Node), or an equivalent per-session limiter on the elicitation hand
Evidence
| 1 | from __future__ import annotations |
| 2 | |
| 3 | from collections.abc import Iterable |
| 4 | from typing import TYPE_CHECKING, Any, Generic |
| 5 | |
| 6 | from pydantic import AnyUrl, BaseModel |
| 7 | |
| 8 | from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext |
| 9 | from mcp.server.elicitation import ( |
| 10 | ElicitationResult, |
| 11 | ElicitSchemaModelT, |
| 12 | UrlElicitationResult, |
| 13 | elicit_url, |
| 14 | elicit_with_validation, |
| 15 | ) |
| 16 | from mcp.server.lowlevel.helper_types import ReadResourceContents |
| 17 | from mcp.types import LoggingLevel |
| 18 | |
| 19 | i |
Remediation
```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @mcp.tool() @limiter.limit("5/minute") async def confirm_action(ctx) -> str: return await ctx.elicit("Continue?", {"yes": "boolean"}) ```
MCP server registers tool handlers that call `elicit()` / `elicitInput()`, but no rate-limit middleware is wired into this file. The MCP elicitation spec SHOULD require clients AND servers to rate-limit elicitation calls โ without it, a compromised client can flood the user with prompts (denial- of-attention) or grind through enumeration attacks against multi-step elicitation flows. Add `slowapi` (Python), `express-rate-limit` (Node), or an equivalent per-session limiter on the elicitation hand
Evidence
| 1 | #!/usr/bin/env python3 |
| 2 | """MCP Everything Server - Conformance Test Server |
| 3 | |
| 4 | Server implementing all MCP features for conformance testing based on Conformance Server Specification. |
| 5 | """ |
| 6 | |
| 7 | import asyncio |
| 8 | import base64 |
| 9 | import json |
| 10 | import logging |
| 11 | |
| 12 | import click |
| 13 | from mcp.server import ServerRequestContext |
| 14 | from mcp.server.mcpserver import Context, MCPServer |
| 15 | from mcp.server.mcpserver.prompts.base import UserMessage |
| 16 | from mcp.server.streamable_http import EventCallback, EventMessage, EventStore |
| 17 | from mcp.type |
Remediation
```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @mcp.tool() @limiter.limit("5/minute") async def confirm_action(ctx) -> str: return await ctx.elicit("Continue?", {"yes": "boolean"}) ```
MCP server has authentication wired but no invocation log. An authenticated server that never logs is a forensics dead-end: unauthorized actions cannot be detected, attributed, or reconstructed after the fact. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Fix: add a structured `logger.info("tool.invoke", ...)` call to every authenticated tool handler with at minimum the tool name, caller identity, and request id. Ship invocation events to a retention sink (CloudWatc
Evidence
| 1 | import json |
| 2 | import time |
| 3 | from typing import Any |
| 4 | |
| 5 | from pydantic import AnyHttpUrl |
| 6 | from starlette.authentication import AuthCredentials, AuthenticationBackend, SimpleUser |
| 7 | from starlette.requests import HTTPConnection |
| 8 | from starlette.types import Receive, Scope, Send |
| 9 | |
| 10 | from mcp.server.auth.provider import AccessToken, TokenVerifier |
| 11 | |
| 12 | |
| 13 | class AuthenticatedUser(SimpleUser): |
| 14 | """User with authentication info.""" |
| 15 | |
| 16 | def __init__(self, auth_info: AccessToken): |
| 17 | super().__init__(auth_info.client_i |
Remediation
Add a structured invocation log to every authenticated MCP tool handler. A minimum example: Python (FastMCP): import logging logger = logging.getLogger(__name__) @mcp.tool() def my_tool(args, ctx): logger.info( "tool.invoke", extra={ "tool": "my_tool", "user_id": ctx.user_id, "request_id": ctx.request_id, }, ) ... TypeScript (@modelcontextprotocol/sdk): import pi
MCP server has authentication wired but no invocation log. An authenticated server that never logs is a forensics dead-end: unauthorized actions cannot be detected, attributed, or reconstructed after the fact. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Fix: add a structured `logger.info("tool.invoke", ...)` call to every authenticated tool handler with at minimum the tool name, caller identity, and request id. Ship invocation events to a retention sink (CloudWatc
Evidence
| 1 | # /// script |
| 2 | # dependencies = [] |
| 3 | # /// |
| 4 | |
| 5 | """MCPServer Text Me Server |
| 6 | -------------------------------- |
| 7 | This defines a simple MCPServer server that sends a text message to a phone number via https://surgemsg.com/. |
| 8 | |
| 9 | To run this example, create a `.env` file with the following values: |
| 10 | |
| 11 | SURGE_API_KEY=... |
| 12 | SURGE_ACCOUNT_ID=... |
| 13 | SURGE_MY_PHONE_NUMBER=... |
| 14 | SURGE_MY_FIRST_NAME=... |
| 15 | SURGE_MY_LAST_NAME=... |
| 16 | |
| 17 | Visit https://surgemsg.com/ and click "Get Started" to obtain these values. |
| 18 | """ |
| 19 | |
| 20 | from typing import Annot |
Remediation
Add a structured invocation log to every authenticated MCP tool handler. A minimum example: Python (FastMCP): import logging logger = logging.getLogger(__name__) @mcp.tool() def my_tool(args, ctx): logger.info( "tool.invoke", extra={ "tool": "my_tool", "user_id": ctx.user_id, "request_id": ctx.request_id, }, ) ... TypeScript (@modelcontextprotocol/sdk): import pi
LLM consensus
MCP server has authentication wired but no invocation log. An authenticated server that never logs is a forensics dead-end: unauthorized actions cannot be detected, attributed, or reconstructed after the fact. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Fix: add a structured `logger.info("tool.invoke", ...)` call to every authenticated tool handler with at minimum the tool name, caller identity, and request id. Ship invocation events to a retention sink (CloudWatc
Evidence
| 1 | """Run from the repository root: |
| 2 | uv run examples/snippets/servers/oauth_server.py |
| 3 | """ |
| 4 | |
| 5 | from pydantic import AnyHttpUrl |
| 6 | |
| 7 | from mcp.server.auth.provider import AccessToken, TokenVerifier |
| 8 | from mcp.server.auth.settings import AuthSettings |
| 9 | from mcp.server.mcpserver import MCPServer |
| 10 | |
| 11 | |
| 12 | class SimpleTokenVerifier(TokenVerifier): |
| 13 | """Simple token verifier for demonstration.""" |
| 14 | |
| 15 | async def verify_token(self, token: str) -> AccessToken | None: |
| 16 | pass # This is where you would implement actual to |
Remediation
Add a structured invocation log to every authenticated MCP tool handler. A minimum example: Python (FastMCP): import logging logger = logging.getLogger(__name__) @mcp.tool() def my_tool(args, ctx): logger.info( "tool.invoke", extra={ "tool": "my_tool", "user_id": ctx.user_id, "request_id": ctx.request_id, }, ) ... TypeScript (@modelcontextprotocol/sdk): import pi
LLM consensus
MCP server has authentication wired but no invocation log. An authenticated server that never logs is a forensics dead-end: unauthorized actions cannot be detected, attributed, or reconstructed after the fact. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Fix: add a structured `logger.info("tool.invoke", ...)` call to every authenticated tool handler with at minimum the tool name, caller identity, and request id. Ship invocation events to a retention sink (CloudWatc
Evidence
| 1 | from dataclasses import dataclass |
| 2 | from typing import Generic, Literal, Protocol, TypeVar |
| 3 | from urllib.parse import parse_qs, urlencode, urlparse, urlunparse |
| 4 | |
| 5 | from pydantic import AnyUrl, BaseModel |
| 6 | |
| 7 | from mcp.shared.auth import OAuthClientInformationFull, OAuthToken |
| 8 | |
| 9 | |
| 10 | class AuthorizationParams(BaseModel): |
| 11 | state: str | None |
| 12 | scopes: list[str] | None |
| 13 | code_challenge: str |
| 14 | redirect_uri: AnyUrl |
| 15 | redirect_uri_provided_explicitly: bool |
| 16 | resource: str | None = None # RFC 8707 resource |
Remediation
Add a structured invocation log to every authenticated MCP tool handler. A minimum example: Python (FastMCP): import logging logger = logging.getLogger(__name__) @mcp.tool() def my_tool(args, ctx): logger.info( "tool.invoke", extra={ "tool": "my_tool", "user_id": ctx.user_id, "request_id": ctx.request_id, }, ) ... TypeScript (@modelcontextprotocol/sdk): import pi
MCP tool file registers a tool, performs a destructive sink (fs.unlink / shutil.rmtree / DROP TABLE / DELETE FROM / TRUNCATE / UPDATE ... SET / HTTP DELETE|PUT|PATCH / subprocess / exec / spawn), and emits no audit event anywhere in the file. Without an audit event, an investigator cannot answer "who deleted record X on day Y?" โ the irreversible action leaves no trail. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Distinct from MCP-201 (no confirmation) and MCP-283
Evidence
| 1 | # /// script |
| 2 | # dependencies = ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector"] |
| 3 | # /// |
| 4 | |
| 5 | # uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector |
| 6 | |
| 7 | """Recursive memory system inspired by the human brain's clustering of memories. |
| 8 | Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient |
| 9 | similarity search. |
| 10 | """ |
| 11 | |
| 12 | import asyncio |
| 13 | import math |
| 14 | import os |
| 15 | from dataclasses import dataclass |
| 16 | from datetime import datetime, timezone |
| 17 | from pathlib import Path |
| 18 | from typing import An |
Remediation
Add a structured audit-event emit immediately after every destructive sink. Minimum schema: actor, action, target, outcome, request_id. Python: from acme.audit import audit_log @mcp.tool() def delete_record(token: str, record_id: str) -> dict: actor = verify_token(token) db.execute("DELETE FROM records WHERE id = %s", (record_id,)) audit_log( actor=actor.sub, action="delete_record", target=record_id, outcome=
LLM consensus
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 304 | # Always try to terminate the process itself as well |
| 305 | try: |
| 306 | process.terminate() |
| 307 | except Exception: |
| 308 | pass |
| 309 | |
| 310 | |
| 311 | @deprecated( |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 166 | async def wait_for_store() -> None: |
| 167 | try: |
| 168 | await self._store.wait_for_update(task_id) |
| 169 | except Exception: |
| 170 | pass |
| 171 | finally: |
| 172 | tg.cancel_scope.cancel() |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 298 | finally: |
| 299 | if win32api: |
| 300 | try: |
| 301 | win32api.CloseHandle(job) |
| 302 | except Exception: |
| 303 | pass |
| 304 | |
| 305 | # Always try to terminate the process itself as well |
| 306 | try: |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 39 | return |
| 40 | |
| 41 | try: |
| 42 | os.killpg(pgid, signal.SIGKILL) |
| 43 | except ProcessLookupError: |
| 44 | pass |
| 45 | |
| 46 | except (ProcessLookupError, PermissionError, OSError) as e: |
| 47 | logger.warning(f"Process group termination failed for PID {pid}: {e}, falling back to simple terminate") |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 174 | async def wait_for_queue() -> None: |
| 175 | try: |
| 176 | await self._queue.wait_for_message(task_id) |
| 177 | except Exception: |
| 178 | pass |
| 179 | finally: |
| 180 | tg.cancel_scope.cancel() |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
echo_tool
add
+3 more โ click to filter
Example
greet
mcp-conformance-test-server
get_weather
book_table
my_tool
textme