From 10db79ec444fe4885d786b3e60783617303dade2 Mon Sep 17 00:00:00 2001 From: betegon Date: Thu, 1 Oct 2026 13:30:59 +0200 Subject: [PATCH] fix(api): accept null legacy event context Allow event responses with explicitly null extra data to load issue details while retaining validation of arrays, scalars, and other event fields. Co-Authored-By: Codex (GPT-6) --- docs/contributing/tool-responses.md | 4 +++ .../src/api-client/client-event.test.ts | 3 +- packages/mcp-core/src/api-client/schema.ts | 3 +- .../tools/catalog/get-issue-details.test.ts | 32 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/contributing/tool-responses.md b/docs/contributing/tool-responses.md index 4665d876e..8910d99a2 100644 --- a/docs/contributing/tool-responses.md +++ b/docs/contributing/tool-responses.md @@ -125,6 +125,10 @@ omits the section. Commit lookup is optional: failures do not prevent issue details from loading, but unexpected server or response-validation failures are reported to Sentry. +The event API's legacy `context` field (extra data) can be `null`. Issue details +must still load; omit the `Extra Data` section for null or empty values while +preserving the event's error, stacktrace, and structured `contexts` data. + ## Structured Content MCP tools may expose `structuredContent` alongside generated text `content`. diff --git a/packages/mcp-core/src/api-client/client-event.test.ts b/packages/mcp-core/src/api-client/client-event.test.ts index b670f665b..287c0318e 100644 --- a/packages/mcp-core/src/api-client/client-event.test.ts +++ b/packages/mcp-core/src/api-client/client-event.test.ts @@ -33,12 +33,12 @@ describe("getEventForIssue context validation", () => { }); it.each([ - { context: null, contextType: "null" }, { context: ["private-extra-value"], contextType: "array" }, { context: "private-extra-value", contextType: "string" }, { context: 12345, contextType: "number" }, { context: false, contextType: "boolean" }, // Valid context can accompany a failure in another field. + { context: null, contextType: "null", title: null }, { context: undefined, contextType: "undefined", title: null }, { context: { "private-extra-key": "private-extra-value" }, @@ -68,6 +68,7 @@ describe("getEventForIssue context validation", () => { it.each([ undefined, + null, {}, { array: [1, "two"], nested: { value: false }, nullable: null }, ])("preserves valid context: %j", async (context) => { diff --git a/packages/mcp-core/src/api-client/schema.ts b/packages/mcp-core/src/api-client/schema.ts index f7b44e3aa..cccc58224 100644 --- a/packages/mcp-core/src/api-client/schema.ts +++ b/packages/mcp-core/src/api-client/schema.ts @@ -1133,7 +1133,8 @@ const BaseEventSchema = z.object({ .optional(), // "context" (singular) is the legacy "extra" field for arbitrary user-defined data // This is different from "contexts" (plural) which are structured contexts - context: z.record(z.string(), z.unknown()).optional(), + // Sentry preserves null when the event's extra data is explicitly null. + context: z.record(z.string(), z.unknown()).nullable().optional(), sdk: z .object({ name: z.string().nullable().optional(), diff --git a/packages/mcp-core/src/tools/catalog/get-issue-details.test.ts b/packages/mcp-core/src/tools/catalog/get-issue-details.test.ts index c6beff31c..7f282caba 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-details.test.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-details.test.ts @@ -8,6 +8,7 @@ import { createRegressedIssue, createUnknownEvent, createUnsupportedIssue, + eventFixture, issueNullCulpritFixture, mswServer, } from "@sentry/mcp-server-mocks"; @@ -2055,6 +2056,37 @@ describe("get_issue_details", () => { expect(result).toContain("### Additional Context"); }); + it("returns event details when legacy context is null", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/6507376925/events/latest/", + () => HttpResponse.json({ ...eventFixture, context: null }), + { once: true }, + ), + ); + + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "CLOUDFLARE-MCP-41", + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + expect(result).toContain("## Event Details"); + expect(result).toContain(`**Event ID**: ${eventFixture.id}`); + expect(result).toContain( + "Error: Tool list_organizations is already registered", + ); + expect(result).toContain("index.js:19631:28 (Object.fetch)"); + expect(result).toContain("### Additional Context"); + expect(result).toContain('name: "cloudflare"'); + expect(result).not.toContain("### Extra Data"); + }); + it("handles regressed performance issues (generic type with empty entries)", async () => { // This tests the actual structure from issue #633 // Regressed performance issues have: