Skip to content

[BUG]: outputSchema declared but no structured output silently passes gateway validation #4208

Description

@jonpspri

🐞 Bug Report

ToolService._extract_and_validate_structured_content (mcpgateway/services/tool_service.py:1340-1347) returns True ("valid") when all of the following are true:

  1. is_error=False.
  2. The tool declares an output_schema.
  3. structured_content is None and no JSON object can be parsed from any TextContent item in content (empty content, a non-text item, or unparseable text).

📜 Spec reference

The MCP 2025-11-25 specification, section "Output Schema" (https://modelcontextprotocol.io/specification/2025-11-25/server/tools#output-schema), states:

If an output schema is provided: Servers MUST provide structured results that conform to this schema.

The current gateway behaviour is documented at tool_service.py:1346-1347 as:

# If no structured data found, treat as valid (nothing to validate)
if structured is None:
    return True

This is lenient by design, but it silently masks upstream servers that violate the spec — the caller receives a success shape even though the declared outputSchema was not satisfied.

🔁 Reproduction (unit test already in repo)

tool_result = ToolResult(content=[], is_error=False)
tool = SimpleNamespace(
    name="test_tool",
    output_schema={"type": "object", "required": ["recognitionId"], "properties": {"recognitionId": {"type": "string"}}},
)
assert tool_service._extract_and_validate_structured_content(tool, tool_result) is True  # ← spec says this should fail

See tests/unit/mcpgateway/services/test_tool_service_coverage.py::TestExtractAndValidateErrorResponses::test_success_with_schema_but_no_content_currently_passes for the regression guard pinning the current (lenient) behaviour.

✅ Proposed fix

When is_error=False and output_schema is declared, treat "no structured payload" as a validation failure equivalent to the existing non-dict structured_content branch at tool_service.py:1314-1327 — set is_error=True on the result and replace content with a validation-error details dict including:

{
  "code": "missing_structured_output",
  "expected": "object",
  "received": "null",
  "message": "outputSchema declared but no structuredContent was returned"
}

This aligns with the MCP Python SDK's server-side validator (mcp/server/lowlevel/server.py:560-564), which produces the analogous message "Output validation error: outputSchema defined but no structured output returned".

🧪 Tests to add / update

  • Flip the pinned test_success_with_schema_but_no_content_currently_passes from asserting the lenient pass to asserting the new strict failure.
  • Add coverage for all three "empty" shapes: content=[], content=[<non-text item>], content=[TextContent(text="null")].
  • Add e2e coverage for REST tools: an upstream REST tool declaring output_schema that returns an empty JSON object {} should surface as isError=true with validation details.

📎 Related

🧠 Scope note

This is a deliberate deferral from PR #4204: tightening success-path validation has a broader blast radius than the #4202 error-path fix and deserves its own review. The lenient behaviour is pinned by a regression test so the change is explicit when it happens.

Metadata

Metadata

Assignees

Labels

SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate releasebugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions