🐞 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:
is_error=False.
- The tool declares an
output_schema.
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.
🐞 Bug Report
ToolService._extract_and_validate_structured_content(mcpgateway/services/tool_service.py:1340-1347) returnsTrue("valid") when all of the following are true:is_error=False.output_schema.structured_contentisNoneand no JSON object can be parsed from anyTextContentitem incontent(emptycontent, 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:
The current gateway behaviour is documented at
tool_service.py:1346-1347as:This is lenient by design, but it silently masks upstream servers that violate the spec — the caller receives a
successshape even though the declaredoutputSchemawas not satisfied.🔁 Reproduction (unit test already in repo)
See
tests/unit/mcpgateway/services/test_tool_service_coverage.py::TestExtractAndValidateErrorResponses::test_success_with_schema_but_no_content_currently_passesfor the regression guard pinning the current (lenient) behaviour.✅ Proposed fix
When
is_error=Falseandoutput_schemais declared, treat "no structured payload" as a validation failure equivalent to the existing non-dictstructured_contentbranch attool_service.py:1314-1327— setis_error=Trueon the result and replacecontentwith 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
test_success_with_schema_but_no_content_currently_passesfrom asserting the lenient pass to asserting the new strict failure.content=[],content=[<non-text item>],content=[TextContent(text="null")].output_schemathat returns an empty JSON object{}should surface asisError=truewith validation details.📎 Related
isError=trueresponses must not be cross-validated againstoutputSchema(this is the complementary tightening on the success path).🧠 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.