Skip to content

fix(mcp): type nullable OpenAPI params by their non-null type - #61

Open
Zernio-Elean wants to merge 1 commit into
developfrom
fix/mcp-nullable-param-types
Open

Zernio-Elean wants to merge 1 commit into
developfrom
fix/mcp-nullable-param-types

Conversation

@Zernio-Elean

@Zernio-Elean Zernio-Elean commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Why

The MCP generator maps OpenAPI 3.1 nullable types (type: [integer, "null"]) and oneOf branches with a null member to the str fallback. On the hosted MCP, 26 params in 16 tools are typed as strings. dedupe_same_text_hours on comment automations cannot be set at all: FastMCP rejects 720 and the API rejects "720".

What

  • get_python_type: a list type resolves to its single non-null member; mixed types keep the str fallback.
  • _resolve_schema: oneOf/anyOf drops null before collapsing, so dm_media, template and branding become dicts.
  • generated_tools.py regenerated (generator plus ruff format, no hand edits): only the 26 param lines and 5 docstring lines change.
  • Tests: nullable integer accepts an int, nullable oneOf object accepts a dict, and a mixed-type list stays str.
  • Every affected API field is strict zod with no coercion, so string inputs were already rejected. No working caller breaks.

Follow-ups

  • MCP still cannot clear a field with an explicit null: _build_payload drops None (separate fix).
  • Port the same change to zernio-mcp if refactor: extract MCP server into standalone repository #29 merges.
  • describe_nested_fields still compares type with ==, so nullable object/array params (placements, contact_info, quick_replies) list no keys in their docstring.
  • POLISH from review: refresh the _resolve_schema docstring (oneOf now drops null branches) and run ruff format on the new tests.

Feedback: feedback_6ae7fd11c0c687e8c3135083

@Zernio-Elean

Copy link
Copy Markdown
Contributor Author

pr-review round 1: APPROVE

Reviewer: opus, fresh context, diff of ea531d5 (92+ / 31-, 3 files)
Gates: pytest 1441 passed, 14 skipped, 1 xfailed, 1 warning in 20.58s; ruff check ok; ruff format 2 files (generator and test file, both already unformatted on origin/develop; the test file adds 3 new hunks); regen byte-identical
Class: normal
Findings:

  • [POLISH] tests/test_generated_mcp_descriptions.py:234,248,250,253: ruff format --diff shows 22 changed lines on origin/develop and 40 on the PR. The new tests have lines over 88 characters. Neighbouring tests at :138 and :165 have the same issue, and CI only runs ruff check.
  • [POLISH] scripts/generate_mcp_tools.py:115-118: the _resolve_schema docstring still says oneOf/anyOf collapse only "if every branch resolves to the same ... type". After :140 drops null branches, that text is out of date.
  • [NOTE] src/late/mcp/generated_tools.py: a fresh regeneration (generator plus ruff format) is byte-identical. There are 26 param lines in 16 tools plus 5 docstring lines, as the body says.
  • [NOTE] Blast radius: each of the 26 new types matches openapi.yaml and the API's strict zod schema (dedupeSameTextHours, discounts, spendCap, campaignIds, defaultLangs, entityType, promotion). No z.coerce on these fields, so old string inputs were already rejected. Mock-client probe: develop raises a ValidationError on dedupe_same_text_hours=720, this PR passes it through.
  • [NOTE] Mutation proof: reverting each generator change makes exactly one test fail. The mixed-type test guards the fallback, not the fix.
  • [NOTE] scripts/generate_mcp_tools.py:344,350: describe_nested_fields still compares type with ==, so [object, null] and [array, null] schemas list no keys in the docstring. Pre-existing gap, follow-up.
  • [NOTE] scripts/generate_mcp_tools.py:138: a oneOf/anyOf branch with a list type would raise TypeError (unhashable list in a set). Pre-existing, the current spec does not hit it, and it would fail loudly at generation time.
  • [NOTE] Smells 1-10 PASS. No new comments. No PII, no em dash, no attribution. No agent-surface paths. Provenance: worktree branch fix/mcp-nullable-param-types.
    Verdict reason: the generator change is minimal and its regeneration reproduces byte-identical, every retyped param matches the strict API schema, each changed line is pinned by a test that fails without it, and only POLISH and NOTE findings remain.

OpenAPI 3.1 nullable params (type: [integer, 'null']) and oneOf branches with a null member fell to the str fallback in the MCP generator, so hosted MCP tools typed 26 params in 16 tools as strings. dedupe_same_text_hours on comment automations could not be set at all: FastMCP rejected 720 and the API rejected "720".

Feedback: feedback_6ae7fd11c0c687e8c3135083
@Zernio-Elean

Copy link
Copy Markdown
Contributor Author

Opus review (fresh context) on head b656bba: APPROVE

This branch has not been deployed

No deployments
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.

1 participant