Repository navigation
Accept null for optional tool arguments that default to None - #71
Conversation
41 arguments on 28 tools were annotated `str`/`int`/... with a `None` default. FastMCP advertises `"default": null` on them but types them non-nullable, so a client that sends the advertised default gets a validation error before the tool runs. Annotate them `Optional[T]` (the style this file already uses) and add an offline schema test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hi @call518, a gentle nudge on this when you have time. It still merges cleanly. The change is annotation-only ( |
|
Thanks for the fix. I reproduced the validation error with an explicit All 169 unit tests passed with the Docker-dependent fixture excluded. The new schema test still inherits that fixture, so it is not fully offline under the normal pytest command. This is an existing test setup issue and can be addressed separately. Although CI results are not available yet, I found no blocking issues. The fix is appropriately scoped and looks good to merge. |
|
Thanks again for the contribution! This fix has been released in v3.4.0. It resolves explicit |
|
Thanks @call518 for reproducing it yourself and for getting it into v3.4.0 so quickly. |
What
41 optional arguments on 28 tools are annotated
str/int/boolwith aNonedefault, e.g.get_table_list(database_name: str = None). FastMCP turns each into a schema that advertises"default": nullon a field whose type excludes null:A client that sends that advertised default back (some agent frameworks and OpenAPI bridges fill in every optional argument) gets a validation error, and the tool never runs.
Reproduction (fastmcp 3.2.0 from
uv.lock, no database running)Calling every tool that has a null-default argument with those arguments set to
null(required ones filled in): 28 of 29 rejected onmain, 0 of 29 with this change.Change
Optional[T], the stylemcp_main.pyalready uses (get_prompt_template,main). Callers who omit them see no difference. The schema becomes{"anyOf": [{"type": "string"}, {"type": "null"}], "default": null}.tests/test_tool_schema_null_defaults.py: offline (no database), lists the tools and checks that every argument defaulting to null accepts null. It fails onmainand passes here. Added to the unit-test step intest.yml.All 41 arguments
get_lock_monitoringdatabase_name,granted,state,mode,locktype,usernameget_current_database_infodatabase_nameget_table_listdatabase_nameget_table_schema_infotable_nameget_database_schema_infoschema_nameget_table_relationshipstable_nameget_pg_stat_statements_top_queriesdatabase_nameget_pg_stat_monitor_recent_queriesdatabase_nameget_table_size_infodatabase_nameget_postgresql_configconfig_name,filter_textget_index_usage_statsdatabase_nameget_vacuum_analyze_statsdatabase_nameget_table_bloat_analysisdatabase_name,schema_name,table_patternget_database_bloat_overviewdatabase_nameget_autovacuum_statusdatabase_name,schema_name,table_patternget_autovacuum_activitydatabase_name,schema_nameget_running_vacuum_operationsdatabase_nameget_vacuum_effectiveness_analysisdatabase_name,schema_nameget_io_statsdatabase_nameget_table_io_statsdatabase_nameget_index_io_statsdatabase_nameget_all_tables_statsdatabase_nameget_user_functions_statsdatabase_nameget_database_conflicts_statsdatabase_nameget_wait_eventsdatabase_name,wait_event_typeget_wal_summarizer_statusdatabase_nameget_async_io_statusdatabase_nameget_per_backend_io_statsdatabase_nameFound with mcp-doctor's
none_default_typecheck, then reproduced as above. If useful, it can scan the rest of the server with no install: open a scan request.🤖 Generated with Claude Code