Skip to content

Commit cb83fe1

Browse files
betegoncodex
andauthored
fix(api): accept null legacy event context (#1353)
Event responses can contain `context: null` for legacy extra data. MCP rejected those responses before `get_issue_details` could return the event's error and stacktrace. Accept null in the shared event schema, preserve validation for records and other fields, and keep the existing formatter behavior that omits empty extra data. Regression tests cover the API client and issue-details handler, including safe diagnostics for other validation failures. ## Validation - Both regressions fail before the fix and pass afterward; 113 focused tests pass. - `pnpm run tsc` and `pnpm run lint` pass. - `pnpm run test` passes in Linux x64 with Node 24 and UTC. - Documentation links and workspace CI selector tests pass. - The packed stdio package installs in a clean consumer; typecheck and runtime import pass. The documentation update selects all workspace projects in CI, exercising the monorepo build and package checks. CI: all builds, MCP tests, CLI unit/E2E tests, CLI package/runtime checks, and the clean stdio consumer pass. The CLI and CLI docs jobs fail only their generated-file checks: the existing period-help generator uses the current month in examples, changing ten generated references. That date-dependent output is separate from this event-schema fix. Co-authored-by: Codex (GPT-6) <noreply@openai.com>
1 parent 3ef57b5 commit cb83fe1

4 files changed

Lines changed: 40 additions & 2 deletions

File tree

‎docs/contributing/tool-responses.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ omits the section. Commit lookup is optional: failures do not prevent issue
125125
details from loading, but unexpected server or response-validation failures
126126
are reported to Sentry.
127127

128+
The event API's legacy `context` field (extra data) can be `null`. Issue details
129+
must still load; omit the `Extra Data` section for null or empty values while
130+
preserving the event's error, stacktrace, and structured `contexts` data.
131+
128132
## Structured Content
129133

130134
MCP tools may expose `structuredContent` alongside generated text `content`.

‎packages/mcp-core/src/api-client/client-event.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ describe("getEventForIssue context validation", () => {
3333
});
3434

3535
it.each([
36-
{ context: null, contextType: "null" },
3736
{ context: ["private-extra-value"], contextType: "array" },
3837
{ context: "private-extra-value", contextType: "string" },
3938
{ context: 12345, contextType: "number" },
4039
{ context: false, contextType: "boolean" },
4140
// Valid context can accompany a failure in another field.
41+
{ context: null, contextType: "null", title: null },
4242
{ context: undefined, contextType: "undefined", title: null },
4343
{
4444
context: { "private-extra-key": "private-extra-value" },
@@ -68,6 +68,7 @@ describe("getEventForIssue context validation", () => {
6868

6969
it.each([
7070
undefined,
71+
null,
7172
{},
7273
{ array: [1, "two"], nested: { value: false }, nullable: null },
7374
])("preserves valid context: %j", async (context) => {

‎packages/mcp-core/src/api-client/schema.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,8 @@ const BaseEventSchema = z.object({
11331133
.optional(),
11341134
// "context" (singular) is the legacy "extra" field for arbitrary user-defined data
11351135
// This is different from "contexts" (plural) which are structured contexts
1136-
context: z.record(z.string(), z.unknown()).optional(),
1136+
// Sentry preserves null when the event's extra data is explicitly null.
1137+
context: z.record(z.string(), z.unknown()).nullable().optional(),
11371138
sdk: z
11381139
.object({
11391140
name: z.string().nullable().optional(),

‎packages/mcp-core/src/tools/catalog/get-issue-details.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createRegressedIssue,
99
createUnknownEvent,
1010
createUnsupportedIssue,
11+
eventFixture,
1112
issueNullCulpritFixture,
1213
mswServer,
1314
} from "@sentry/mcp-server-mocks";
@@ -2055,6 +2056,37 @@ describe("get_issue_details", () => {
20552056
expect(result).toContain("### Additional Context");
20562057
});
20572058

2059+
it("returns event details when legacy context is null", async () => {
2060+
mswServer.use(
2061+
http.get(
2062+
"https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/6507376925/events/latest/",
2063+
() => HttpResponse.json({ ...eventFixture, context: null }),
2064+
{ once: true },
2065+
),
2066+
);
2067+
2068+
const result = await getIssueDetails.handler(
2069+
{
2070+
organizationSlug: "sentry-mcp-evals",
2071+
issueId: "CLOUDFLARE-MCP-41",
2072+
eventId: undefined,
2073+
issueUrl: undefined,
2074+
regionUrl: null,
2075+
},
2076+
baseContext,
2077+
);
2078+
2079+
expect(result).toContain("## Event Details");
2080+
expect(result).toContain(`**Event ID**: ${eventFixture.id}`);
2081+
expect(result).toContain(
2082+
"Error: Tool list_organizations is already registered",
2083+
);
2084+
expect(result).toContain("index.js:19631:28 (Object.fetch)");
2085+
expect(result).toContain("### Additional Context");
2086+
expect(result).toContain('name: "cloudflare"');
2087+
expect(result).not.toContain("### Extra Data");
2088+
});
2089+
20582090
it("handles regressed performance issues (generic type with empty entries)", async () => {
20592091
// This tests the actual structure from issue #633
20602092
// Regressed performance issues have:

0 commit comments

Comments
 (0)