Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Security

- **`mcp` 2.0.0 → 2.2.0** (GHSA-84m7-p3x7-pcfv, CVE-2026-59951: unbounded session accumulation in the StreamableHTTP
transport). (Dependabot #356)

### Fixed

- **Tool error messages survive `mcp` 2.2.0.** The SDK now replaces the message of any exception that is not a
`ToolError` with a generic "Error executing tool <name>". MCPg's own errors (`MCPgError` subclasses, e.g. an
unconfigured NL→SQL provider that names the env var to set) are re-raised as `ToolError` by the call wrapper, and the
audit log records their text; the heavy-diagnostics gate raises `ToolError`. Unexpected exceptions keep the SDK's
generic message.

## [0.8.3] - 2026-10-14

A feature release by content (new tools and opt-in flags, nothing removed or renamed), shipped as a patch per the maintainer's call: the default tool surface only gains five read-only tools, and every new write or admin tool needs an explicit `MCPG_ALLOW_*` flag. See [release notes](docs/release-notes-0.8.3.md).
Expand Down
13 changes: 13 additions & 0 deletions src/mcpg/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from mcpg.context import AppContext
from mcpg.cursors import CursorManager
from mcpg.database import Database
from mcpg.errors import MCPgError
from mcpg.listen import ListenManager
from mcpg.middleware.rate_limit import RateLimiter
from mcpg.observability import get_metrics
Expand Down Expand Up @@ -204,6 +205,18 @@ async def call_tool(
)
metrics.record_call(name, "error", duration, bucket=bucket)
raise friendly_exc from exc
# Since mcp 2.2.0 the SDK wraps any exception that is not a
# ToolError in UnexpectedToolError("Error executing tool <name>")
# and drops its message (unexpected errors can leak internals).
# MCPgError messages are written for the client ("provider
# 'openai' is not configured ...; set OPENAI_API_KEY"), so they
# go back as a ToolError, and the audit log gets the real text.
domain_error = exc if isinstance(exc, MCPgError) else exc.__cause__
if isinstance(domain_error, MCPgError):
tool_exc = ToolError(str(domain_error))
audit.record(audit.AuditEvent(tool=name, arguments=arguments, status="error", error=str(tool_exc)))
metrics.record_call(name, "error", duration, bucket=bucket)
raise tool_exc from domain_error
audit.record(audit.AuditEvent(tool=name, arguments=arguments, status="error", error=str(exc)))
metrics.record_call(name, "error", duration, bucket=bucket)
raise
Expand Down
9 changes: 7 additions & 2 deletions src/mcpg/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing import Annotated, Any, TypeVar, cast

from mcp.server.mcpserver import Context, MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations
from pydantic import Field

Expand Down Expand Up @@ -245,10 +246,14 @@ async def _cached_call( # noqa: UP047


def _check_heavy_diagnostics(ctx: _Ctx, tool_name: str) -> None:
"""Raise a RuntimeError if heavy diagnostics are disabled by settings."""
"""Raise a ToolError if heavy diagnostics are disabled by settings.

A ToolError, not a RuntimeError: since mcp 2.2.0 the SDK replaces the message
of any other exception with a generic "Error executing tool ..." text, and this
message is meant for the client."""
settings = ctx.request_context.lifespan_context.settings
if not settings.enable_heavy_diagnostics:
raise RuntimeError(
raise ToolError(
f"The tool {tool_name!r} has been disabled by the server administrator "
"(MCPG_ENABLE_HEAVY_DIAGNOSTICS is set to false)."
)
Expand Down
12 changes: 6 additions & 6 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading