MCP: emit Gemini-compatible tool schemas (#1074)#1082
Merged
Conversation
Stateless transport (#1077) was necessary but not sufficient for Google Antigravity (Gemini) to list the MCP tools. The remaining blocker is parameter schema shape: a nullable-with-default parameter such as `string? server_name = null` is emitted as `{ "type": ["string","null"], "default": null }`. Gemini's function-declaration validator rejects union `type` arrays and the `default` keyword and drops the ENTIRE tool set, so the client connects but lists zero tools. Claude Code / opencode are lenient and accept the same schema, which is why this only bites Gemini-based clients. Rather than hand-edit ~122 nullable params across the tool classes, add a schema transform at registration time: - PerformanceMonitor.Common/Mcp/McpSchemaCompat.cs: new `WithGeminiCompatibleTools<T>()`, a drop-in for the SDK's `WithTools<T>()` that registers tools identically (same DI service-parameter exclusion and invocation path) but installs a TransformSchemaNode that collapses nullable type unions (`["string","null"]` -> `"string"`) and strips the `default` keyword. - Lite/Mcp/McpHostService.cs and Dashboard/Mcp/McpHostService.cs: register every tool class via WithGeminiCompatibleTools instead of WithTools. - PerformanceMonitor.Common.csproj: add the ModelContextProtocol package reference required by the new helper. Behavior is unchanged for lenient clients: optionality is still conveyed by the schema's `required` array and the .NET default still applies when an argument is omitted -- only the wire-format of the schema changes. Verified with golden-sample tests (Lite.Tests + Dashboard.Tests) that assert against the actual tools/list InputSchema: no union types or default keywords remain, the stock path still produces them (proving the transform is necessary), and the tool set is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Follow-up to #1077. Stateless transport was necessary but not sufficient — Google Antigravity (Gemini) still listed zero MCP tools. @gotqn confirmed by testing the
devbuild that the remaining blocker is cause #2: parameter schema shape.A nullable-with-default parameter like
string? server_name = nullis emitted as:{ "type": ["string", "null"], "default": null }Gemini's function-declaration validator rejects union
typearrays and thedefaultkeyword and drops the entire tool set. Claude Code / opencode are lenient and accept the same schema, which is why this only bites Gemini-based clients.Fix
Rather than hand-edit ~122 nullable params across the tool classes, transform the schema at registration time.
PerformanceMonitor.Common/Mcp/McpSchemaCompat.cs— newWithGeminiCompatibleTools<T>(), a drop-in for the SDK'sWithTools<T>(). Registers tools identically (same DI service-parameter exclusion and invocation path — mirrors the SDK v1.3.0WithTools<T>source) but installs aTransformSchemaNodethat collapses nullable type unions (["string","null"]→"string") and strips thedefaultkeyword.Lite/Mcp/McpHostService.cs,Dashboard/Mcp/McpHostService.cs— register every tool class via the new helper.PerformanceMonitor.Common.csproj— add theModelContextProtocolpackage reference the helper needs.Behavior is unchanged for lenient clients: optionality is still conveyed by the schema's
requiredarray and the .NET default still applies when an argument is omitted — only the wire-format changes.Test plan
Lite.Tests, 3 inDashboard.Tests) assert against the actualtools/listInputSchema:typearrays ordefaultkeywords remainWithToolspath does still emit them (proves the transform is necessary and guards against SDK drift)type/properties/required; nothing else Gemini-hostile ($schema,additionalProperties,anyOf, …)devbuild🤖 Generated with Claude Code