Skip to content

MCP: emit Gemini-compatible tool schemas (#1074)#1082

Merged
erikdarlingdata merged 1 commit into
devfrom
feature/1074-mcp-gemini-schema-compat
Jun 8, 2026
Merged

MCP: emit Gemini-compatible tool schemas (#1074)#1082
erikdarlingdata merged 1 commit into
devfrom
feature/1074-mcp-gemini-schema-compat

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

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 dev build that the remaining blocker is cause #2: parameter schema shape.

A nullable-with-default parameter like 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. 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 — new WithGeminiCompatibleTools<T>(), a drop-in for the SDK's WithTools<T>(). Registers tools identically (same DI service-parameter exclusion and invocation path — mirrors the SDK v1.3.0 WithTools<T> source) but installs a TransformSchemaNode that collapses nullable type unions (["string","null"]"string") and strips the default keyword.
  • Lite/Mcp/McpHostService.cs, Dashboard/Mcp/McpHostService.cs — register every tool class via the new helper.
  • PerformanceMonitor.Common.csproj — add the ModelContextProtocol package reference the helper needs.

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 changes.

Test plan

  • Lite, Dashboard, and Common build clean (0 warnings / 0 errors)
  • Golden-sample tests (3 in Lite.Tests, 3 in Dashboard.Tests) assert against the actual tools/list InputSchema:
    • no union type arrays or default keywords remain
    • the stock WithTools path does still emit them (proves the transform is necessary and guards against SDK drift)
    • the advertised tool set is unchanged (no tools dropped/added)
  • Dumped a real transformed schema — the only top-level keys across all tools are type / properties / required; nothing else Gemini-hostile ($schema, additionalProperties, anyOf, …)
  • @gotqn to confirm Antigravity lists the tools on the next dev build

🤖 Generated with Claude Code

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>
@erikdarlingdata erikdarlingdata merged commit 0885fe0 into dev Jun 8, 2026
2 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/1074-mcp-gemini-schema-compat branch June 8, 2026 16:20
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