From 1cda59f6f279e87fdb490616a81ef6359c0a7c00 Mon Sep 17 00:00:00 2001 From: er-s-an Date: Fri, 11 Sep 2026 02:40:02 +0800 Subject: [PATCH] fix(server): don't crash registerAppTool when config._meta is omitted ToolConfig._meta is optional (_meta?), but the UI-metadata normalization read config._meta.ui unconditionally, so registering a UI-less tool via registerAppTool threw TypeError: Cannot read properties of undefined (reading 'ui') on the first request. Default to {} before normalizing. Adds a regression test. --- src/server/index.test.ts | 28 ++++++++++++++++++++++++++++ src/server/index.ts | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 489d0b0de..14b89a50d 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -16,6 +16,34 @@ import { Client } from "@modelcontextprotocol/client"; import { z } from "zod/v4"; describe("registerAppTool", () => { + it("registers a tool without _meta (optional per ToolConfig)", () => { + let capturedConfig: Record | undefined; + const mockServer = { + registerTool: mock( + (name: string, config: Record, handler: unknown) => { + capturedConfig = config; + }, + ), + registerResource: mock(() => {}), + }; + + const handler = async () => ({ + content: [{ type: "text" as const, text: "ok" }], + }); + + // No _meta at all — previously crashed reading `.ui` of undefined. + expect(() => + registerAppTool( + mockServer as unknown as Pick, + "plain-tool", + { title: "Plain Tool", description: "No UI metadata" }, + handler, + ), + ).not.toThrow(); + expect(mockServer.registerTool).toHaveBeenCalledTimes(1); + expect(capturedConfig?._meta).toEqual({}); + }); + it("should pass through config to server.registerTool", () => { let capturedName: string | undefined; let capturedConfig: Record | undefined; diff --git a/src/server/index.ts b/src/server/index.ts index 5fad055a0..8fc058347 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -284,7 +284,8 @@ export function registerAppTool( // Normalize metadata for backward compatibility: // - If _meta.ui.resourceUri is set, also set the legacy flat key // - If the legacy flat key is set, also set _meta.ui.resourceUri - const meta = config._meta; + // `ToolConfig._meta` is optional; default it before reading `meta.ui`. + const meta = config._meta ?? {}; const uiMeta = meta.ui as McpUiToolMeta | undefined; const legacyUri = meta[RESOURCE_URI_META_KEY] as string | undefined;