Repository navigation
Tool schemas with a root-level anyOf / oneOf render no input fields #2123
Description
Which version line? v2 — current (@modelcontextprotocol/inspector@latest)
Which client? All / shared core
Inspector version: 2.3.0
Node version: v24.19.0
Operating system (and browser, for the web client): Ubuntu 26.04 LTS, Chrome 151.0.7922.169
Transport: stdio
MCP server under inspection
A two-tool server written for this report, on @modelcontextprotocol/server@2.0.0. It serves both
protocol eras, so one server covers the legacy and modern handshakes.
Server: node server.mjs (local, purpose-built)
Protocol era: reproduced on BOTH — legacy (2025-11-25) and modern (2026-07-28)
OAuth: no
// server.mjs
import { McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
const EMAIL = {
type: "object",
properties: {
kind: { type: "string", const: "email" },
address: { type: "string" },
},
required: ["kind", "address"],
};
const SMS = {
type: "object",
properties: {
kind: { type: "string", const: "sms" },
phone: { type: "string" },
},
required: ["kind", "phone"],
};
const createServer = () => {
const server = new McpServer({ name: "union-repro", version: "0.0.1" });
server.registerTool(
"union_anyof",
{
description: "Discriminated union at the root of inputSchema (anyOf).",
inputSchema: fromJsonSchema({ type: "object", anyOf: [EMAIL, SMS] }),
},
async () => ({ content: [{ type: "text", text: "called union_anyof" }] }),
);
server.registerTool(
"union_oneof",
{
description: "Discriminated union at the root of inputSchema (oneOf).",
inputSchema: fromJsonSchema({
type: "object",
discriminator: { propertyName: "kind" },
oneOf: [EMAIL, SMS],
}),
},
async () => ({ content: [{ type: "text", text: "called union_oneof" }] }),
);
return server;
};
await serveStdio(createServer);Steps to reproduce
- Save the file above as
server.mjsand install@modelcontextprotocol/servernext to it. - Save this as
catalog.json(a writable catalog —--configis read-only, and the Protocol Era
setting in step 5 cannot be changed in a read-only session):{ "mcpServers": { "union-repro": { "type": "stdio", "command": "node", "args": ["./server.mjs"] } } } - Run
npx @modelcontextprotocol/inspector@2.3.0 --web --catalog catalog.jsonand connect to
union-repro. The badge readsMCP 2025-11-25and the message log is taggedLEGACY. - Open the Tools tab, then select
union_anyofandunion_oneofin turn. - Disconnect. Open the server's Settings → Options → Protocol Era and choose
Modern (2026-07-28, sessionless). Reconnect — the badge now readsMCP 2026-07-28, the log is
taggedMODERN, andSERVER/DISCOVERappears in the protocol log. - Repeat step 4.
Expected behavior
A form that lets the arguments be supplied — a picker for the anyOf / oneOf branch, or the
raw-JSON editor that a union-typed property already falls back to.
The 2026-07-28 revision makes this explicit for inputSchema: "Tool arguments are always JSON
objects, so type: "object" is required at the root. Beyond that, any JSON Schema 2020-12 keyword
may appear alongside type — including composition keywords (oneOf, anyOf, allOf, not)"
(schema.ts).
Both tools above are that shape.
Actual behavior
On both protocol eras, both tools render only the description and the Execute Tool button. No
input controls of any kind appear: no branch picker, no fields, no raw-JSON editor. Pressing Execute
Tool can only send empty arguments, so neither tool can be exercised from the Inspector.
Logs, errors, or screenshots
No console errors, and no failed requests — tools/list returns both tools with their schemas intact
and the form simply renders nothing. Counting the form controls in the tool detail panel:
| tool | era | rendered form controls |
|---|---|---|
union_anyof |
legacy 2025-11-25 |
0 |
union_oneof |
legacy 2025-11-25 |
0 |
union_anyof |
modern 2026-07-28 |
0 |
union_oneof |
modern 2026-07-28 |
0 |
On the modern era the connection badge reads MCP 2026-07-28, the message log is tagged MODERN,
and SERVER/DISCOVER appears in the protocol log — with the tool panel still showing nothing but
the Execute Tool button. I can attach a screenshot in a comment if useful.
Already prototyped a fix?
No.
Activity
ivan-kovatchev commentedon Aug 25, 2026
Example screenshot
cliffhall commentedon Aug 26, 2026
Triage: Priority Medium (total 7)
buglabelBoard: #28, Status
Incoming(awaiting maintainer review — no milestone yet).fix: render tool schemas with root-level anyOf/oneOf composition