Skip to content

fix(arcade-mcp-server): put an array's enum on its items, not on the array - #939

Merged
david-arcade-dev merged 1 commit into
mainfrom
david/fix-array-enum-output-schema
Sep 21, 2026
Merged

david-arcade-dev merged 1 commit into
mainfrom
david/fix-array-enum-output-schema

Conversation

@david-arcade-dev

@david-arcade-dev david-arcade-dev commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

_value_schema_to_json_schema copied a ValueSchema.enum to the array level when val_type == "array". On an array that enum is an allow-list for the elements; at the array level JSON Schema reads it as "the whole array must equal one of these strings", which no array satisfies — not even an empty one.

list[Literal["a", "b"]]
  before  {"type": "array", "enum": ["a","b"], "items": {"type": "string"}}
  after   {"type": "array", "items": {"type": "string", "enum": ["a","b"]}}

Any MCP client that validates structured output rejected every response from a tool whose output names such a field, with Structured content does not match the tool's output schema: data/.../<field> must be equal to one of the allowed values. The write had already happened upstream; only the response was lost, so the agent could not read its own result. Input parameters typed the same way (list[SomeStrEnum]) carried the same malformed schema.

This is the rule the Engine's Go renderer already follows for the same ValueSchema, so the two paths now publish the same schema for the same tool.

Design decisions

  • Scalar Literal/Enum keeps its enum where it is — only arrays move.
  • A nullable array with an enum no longer gets None appended to an array-level enum by _apply_nullable; the null goes on type, which is what nullable means.

Test plan

Six tests in libs/tests/arcade_mcp_server/test_convert.py, each failing before the change:

  • test_output_schema_array_enum_lands_on_items
  • test_input_schema_array_enum_lands_on_items
  • test_output_schema_nullable_array_enum_keeps_elements_non_null
  • test_output_schema_list_of_literal_constrains_elements — end to end through ToolCatalog
  • test_output_schema_list_of_enum_constrains_elements — list[SomeStrEnum], the other source of an array-level enum
  • test_output_schema_nested_list_of_literal_constrains_elements — the field inside an object output, the shape that actually broke in the wild

uv run pytest libs/tests -> 3944 passed, 1 skipped.

🤖 Generated with Claude Code


Note

Low Risk
Corrects published JSON Schema for array-of-enum tools; reduces client validation failures with no auth or data-handling changes.

Overview
Fixes MCP tool schema generation so ValueSchema.enum on array types applies to items, not the array root. Previously, list[Literal[…]] / list[StrEnum] produced {"type": "array", "enum": [...]}, which JSON Schema interprets as “the entire value must be one of these scalars”—so MCP clients validating structured output rejected otherwise valid tool responses (and the same mistake affected input parameters).

Scalar and object enums are unchanged; nullable arrays still use type: ["array", "null"] without polluting element constraints. arcade-mcp-server is bumped to 1.31.1, with new unit and end-to-end tests (including nested object fields and ToolCatalog paths).

Reviewed by Cursor Bugbot for commit 368a12e. Bugbot is set up for automated code reviews on this repo. Configure here.

…array

A list[Literal[...]] or list[SomeEnum] arrives as one ValueSchema with
val_type="array", inner_val_type="string" and enum=[...]. That enum is an
allow-list for the elements, but it was copied to the array level, where
JSON Schema reads it as "the whole array must equal one of these strings" --
an assertion no array satisfies, not even an empty one.

Every MCP client that validates structured output therefore rejected every
response from a tool whose output names such a field, with
"data/.../<field> must be equal to one of the allowed values". The write
had already happened; only the response was lost.

Input parameters typed the same way carried the same malformed schema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
...ibs/arcade-mcp-server/arcade_mcp_server/convert.py 96.50% <100.00%> (+0.07%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@david-arcade-dev
david-arcade-dev merged commit 6e4d67c into main Sep 21, 2026
49 checks passed
@david-arcade-dev
david-arcade-dev deleted the david/fix-array-enum-output-schema branch September 21, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants