[BUG] Valid MCP tool silently dropped: API schema validation rejects property names containing `[]`

Status Open
Reported on v2.1.263
Maintainer reply None cached
Activity 4 comments · opened Sep 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

A tool from a spec-compliant MCP server is silently discarded at session start because its
input schema contains a property named uids[]. Tool schemas are validated against
^[a-zA-Z0-9_.-]{1,64}$ for property keys, and the square brackets fail that pattern.

uids[] is a legal property name in JSON Schema — the spec places no constraints on the keys
of properties — and the MCP specification defines inputSchema as a JSON Schema object.
So the server is valid on both counts; the rejection comes from a stricter constraint layered
on top by the API. The result is that a working MCP server loses functionality with no
actionable error surfaced to the user.

Environment

  • Claude Code: 2.1.263 (VS Code extension)
  • Platform: Linux 7.0.12-linuxkit (devcontainer), Node v24.19.0
  • MCP server: pushpad-mcp@0.1.1 (stdio, npx -y pushpad-mcp)
  • Affected tool: subscriptions_list

Reproduction

  1. Configure the MCP server in .mcp.json:

``json
{
"mcpServers": {
"pushpad": {
"type": "stdio",
"command": "npx",
"args": ["-y", "pushpad-mcp"],
"env": { "PUSHPAD_TOKEN": "<token>" }
}
}
}
``

  1. Start a session and ask the model to list subscriptions for a project.

Expected

subscriptions_list is available, like the server's other 18 tools.

Actual

The tool is excluded before the model ever sees it. The only trace is an internal note:

- "subscriptions_list" (MCP server "pushpad"): "property key uids[] does not match /^[a-zA-Z0-9_.-]{1,64}$/"

Root cause

pushpad-mcp is a generic OpenAPI-to-MCP bridge; it fetches https://pushpad.xyz/openapi.yaml
at startup and derives tools from it. The spec declares:

uids:
  in: query
  name: uids[]
  style: form
  explode: true
  schema:
    type: array
    items:
      type: string

This is valid OpenAPI 3 and the standard way to express a repeated bracketed query parameter
(?uids[]=a&uids[]=b), the convention used by Rails, PHP, and many other backends. The bridge
maps the query-parameter name directly to a JSON Schema property name, yielding uids[].

subscriptions_list is the only one of the spec's 19 operations that references a bracketed
parameter, which is exactly why it is the only tool dropped — the failure is entirely attributable
to the character class, not to anything else about the server.

Impact

  • Any OpenAPI-derived MCP server exposing bracket-style array query params loses those tools.

This is a widespread convention, so the blast radius is larger than one server.

  • The failure is silent from the user's perspective. Nothing appears in the UI: the tool simply

does not exist, and the model can only report it as missing after the fact.

  • There is no user-side remedy. Fixing it requires changing the upstream API's parameter naming

or patching the bridge — neither is available to someone who just installed the server.

Suggested resolutions

Any one of these would resolve it:

  1. Relax the property-key pattern to accept the characters JSON Schema permits, or at minimum

[ and ]. (recommended)

  1. Sanitize on the client side. Rewrite non-conforming keys to a safe alias when registering

the tool, and map them back when invoking it. This keeps the constraint intact while preserving
compatibility with compliant servers.

What Should Happen?

API schema validation should allow property names containing []

Error Messages/Logs

Steps to Reproduce

  1. Configure the MCP server in .mcp.json:

``json
{
"mcpServers": {
"pushpad": {
"type": "stdio",
"command": "npx",
"args": ["-y", "pushpad-mcp"],
"env": { "PUSHPAD_TOKEN": "<token>" }
}
}
}
``

  1. Start a session and ask the model to list subscriptions for a project.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.263 (VS Code extension)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

3 Comments

veriton-dev · 1 month ago

Hitting the same class of failure on OpenAPI→MCP bridges: the server is fine, the host applies a stricter key charset than JSON Schema, and the only observable outcome is a missing tool.

A few host-ops notes that have helped when debugging similar silent drops:

1. Distinguish three failure modes

| Class | When | What the model sees | What ops needs |
|---|---|---|---|
| schema_reject | register / list_tools | tool absent | key + regex + server name |
| invoke_reject | call_tool | explicit error | mapped alias vs wire name |
| transport_fail | stdio/http | timeout/crash | not a schema problem |

Right now this is pure schema_reject, but it presents like "the server never implemented subscriptions_list." That is the worst of the three for triage.

2. Prefer sanitize-on-register over silent drop

If the upstream API key charset cannot accept [], the client can still preserve the tool:

  1. Detect non-conforming property keys at registration.
  2. Rewrite to a stable safe alias (uids[] → uids_array or uids).
  3. Keep a bidirectional map: model/alias ↔ wire/OpenAPI name.
  4. On tools/call, reverse-map args before forwarding to the MCP server.
  5. Emit one structured warning (not just an internal note):

tool=subscriptions_list server=pushpad key="uids[]" alias="uids_array" reason=property_key_charset.

That keeps OpenAPI-form explode array params (uids[]=a&uids[]=b) working without requiring every bridge author to rename query params.

3. Surface, don't swallow

A dropped tool should show up in the same place operators already look for MCP health (server status / tool count / session init log). A single internal line that never reaches the UI guarantees the model will invent a story about a missing capability.

Minimum signal:

  • tools advertised by server: N
  • tools accepted by host: M
  • rejected: [{name, key, pattern, suggestion}]

If N≠M, fail loud at session start (or at least badge the server), not mid-conversation when the user asks for the one dropped operation.

4. OpenAPI bridges will keep producing this

Bracketed array query names are normal in Rails/PHP/OpenAPI style: form, explode: true. Any generic OpenAPI→MCP generator that copies parameter name into JSON Schema properties will hit [], and occasionally other legal-but-strict-charset characters. Treating that as "invalid server" is backwards; the bridge is following OpenAPI, the host is applying an API-side constraint.

Acceptance checks (if fixing)

  • [ ] MCP server with one property key uids[] still lists the tool to the model
  • [ ] tools/call with the model-facing alias round-trips to the wire name the server expects
  • [ ] session init reports rejected keys with server + tool + key + pattern (not silent)
  • [ ] tools without bracket keys are unchanged (no alias churn)
  • [ ] regression: property keys with _, ., - still pass as today

Happy to retest against pushpad-mcp / any OpenAPI-derived fixture once a sanitize or relaxed-pattern path lands.

cid2230 · 27 days ago

Withdrawn (2026-09-27). This earlier commercial note from Exactlane Operations, including the price quoted here, is withdrawn and no longer valid. It should not have been posted on this issue — apologies for the noise.

— Exactlane Operations

cid2230 · 17 days ago

Follow-up on pushpad-mcp@0.1.1 with the Claude Code 2.1.282 CLI (local server, no Pushpad API call).

19 tools listed; subscriptions_list is still the only one dropped. The log names uids[], but the same input schema also has tags[], which fails the same /^[a-zA-Z0-9_.-]{1,64}$/ check. A constructed variant with uids[] removed and tags[] kept was still dropped:

property key "tags[]" does not match /^[a-zA-Z0-9_.-]{1,64}$/

So renaming only uids[] would leave the tool dropped.

Proposed bridge fix (not invocation-tested): alias both schema keys to legal names (uids, tags) while keeping the bracketed names uids[] / tags[] on the wire for the query string.

Showing cached comments. Read the full discussion on GitHub ↗