From 89a109560e94665a69fbcd6fbace56ac3653d1f8 Mon Sep 17 00:00:00 2001 From: jubnl Date: Fri, 10 Apr 2026 03:03:37 +0200 Subject: [PATCH] fix(mcp): return deprecation notice as isError tool result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isError: true is the one MCP mechanism Claude.ai cannot ignore — it is obligated to surface tool errors to the user. On the first tool call of a static-token session, return only the deprecation notice with isError: true (no data). The per-session _noticeEmitted flag is set before returning, so the immediate retry (or any subsequent call) goes through normally and returns real data. --- server/src/mcp/tools/trips.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/mcp/tools/trips.ts b/server/src/mcp/tools/trips.ts index 3a178a32..24faf662 100644 --- a/server/src/mcp/tools/trips.ts +++ b/server/src/mcp/tools/trips.ts @@ -130,10 +130,9 @@ export function registerTripTools(server: McpServer, userId: number, scopes: str }, async ({ include_archived }) => { const notice = getDeprecationNotice(); + if (notice) return { isError: true as const, content: [{ type: 'text' as const, text: notice }] }; const trips = listTrips(userId, include_archived ? null : 0); - // Embed notice as a top-level JSON field so Claude processes it as data, - // not as a separate content annotation it can silently ignore. - return ok({ ...(notice ? { _deprecation_notice: notice } : {}), trips }); + return ok({ trips }); } ); @@ -171,8 +170,8 @@ export function registerTripTools(server: McpServer, userId: number, scopes: str messageCount = countMessages(tripId); } const notice = getDeprecationNotice(); + if (notice) return { isError: true as const, content: [{ type: 'text' as const, text: notice }] }; return ok({ - ...(notice ? { _deprecation_notice: notice } : {}), ...summary, reservations: canReadRes ? summary.reservations : undefined, packing: canReadPacking ? summary.packing : undefined,