Skip to content

fix(codegen): annotate optional tool parameters as Optional - #105

Open
vishalhabib99 wants to merge 1 commit into
cnoe-io:mainfrom
vishalhabib99:fix/optional-params-accept-null
Open

vishalhabib99 wants to merge 1 commit into
cnoe-io:mainfrom
vishalhabib99:fix/optional-params-accept-null

Conversation

@vishalhabib99

Copy link
Copy Markdown

Description

The generator emits optional query and body parameters as name: T = None. FastMCP turns that into an input schema with "default": null on a type that doesn't allow null. A client that sends the advertised default explicitly gets an input validation error, and the tool never runs.

Example from the committed examples/argocd server (fastmcp 2.11.1, built in memory, no network):

get_application_svc_ls.param_project schema = {"default": null, "items": {"type": "string"}, "title": "Param Project", "type": "array"}
call_tool("get_application_svc_ls", {"param_project": None})
-> Input validation error: None is not of type 'array'

In the committed examples, this affects 79 tools in argocd, 59 in argo-workflows, 28 in splunk and 6 in argo-rollouts.

Fix: emit Optional[T] = None for optional parameters, which also covers the dict-mode body parameter. This touches 4 f-strings in mcp_codegen.py, at the 4 places that write = None. tools/tool.tpl already imports Optional, so the generated modules need no other change. This is the change requested in #42, which was closed as stale without a fix.

Test: test_optional_parameters_accept_explicit_null generates a tool module offline from a small spec with optional query and body parameters, registers it on FastMCP, and calls it in memory with each optional argument set to None. The API client is stubbed, so no LLM or HTTP call is made. The test fails on main (param_limit=None rejected: Input validation error: None is not of type 'integer') and passes with this change. The assertion in test_extract_body_params_simple is updated to match the new signature string.

Suite: on main, the suite gives 23 failed and 1 passed. With this change it gives 23 failed and 2 passed, and the 23 failures are the same. They are pre-existing and unrelated: the setup_env fixture points at examples/petstore/openapi_petstore.json, but the file is openapi-petstore.json. test_cli_accepts_enable_slim also fails on the generate-mcp subcommand. The new test doesn't use that fixture, so it runs either way.

Not included: the committed examples/*/generate_code are not regenerated. Running the current generator on main offline (no LLM) already gives a large diff against the committed argocd tools, unrelated to this change, so regenerating here would bury the fix. After this change, a fresh offline run on examples/argocd/openapi-argocd.json gives 0 tools with this issue, down from 79. Maintainers can regenerate the examples whenever suits them.

Type of Change

  • Bugfix
  • New Feature
  • Breaking Change
  • Refactor
  • Documentation
  • Other (please describe)

Checklist

  • I have read the contributing guidelines
  • Existing issues have been referenced (where applicable)
  • I have verified this change is not present in other open pull requests
  • Functionality is documented
  • All code style checks pass (ruff: no new findings; the 2 existing ones in mcp_codegen.py, lines 188 and 1402, are untouched)
  • New code contribution is covered by automated tests
  • All new and existing tests pass

Optional query and body parameters were emitted as `name: T = None`, so
every generated MCP tool advertised `"default": null` on a type that
excludes null. A client that sends that default explicitly gets an input
validation error and the tool never runs. Emit `Optional[T] = None`
instead (tool modules already import Optional).

Adds a regression test that generates a tool module offline, registers
it on FastMCP and calls it with each optional argument set to None.

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