Skip to content

Commit 1f9f928

Browse files
dcramerclaude
andauthored
fix(tools): handle null values in IssueTagValuesSchema (#708)
## Summary Fixes ZodError validation failures in the `get_issue_tag_values` tool when Sentry API returns null values for `name` and `value` fields in the `topValues` array. **Issue:** https://sentry.sentry.io/issues/7181206453/ ### Key Changes - Made `name`, `value`, and `key` fields nullable in `IssueTagValueSchema` to match actual API responses - Added null fallback in handler to display `"(null)"` for null values - Added test case to verify null value handling ### Root Cause The Sentry API can return `null` for tag value entries (observed in production with 19 occurrences affecting 14 users). The Zod schema expected strings, causing validation failures. Fixes MCP-SERVER-EZR Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 44bc95a commit 1f9f928

3 files changed

Lines changed: 59 additions & 6 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,9 @@ export const EventAttachmentListSchema = z.array(EventAttachmentSchema);
672672
* Represents a single value's occurrence count and percentage within a tag.
673673
*/
674674
export const IssueTagValueSchema = z.object({
675-
key: z.string().optional(),
676-
name: z.string().optional(),
677-
value: z.string(),
675+
key: z.string().nullable().optional(),
676+
name: z.string().nullable().optional(),
677+
value: z.string().nullable(),
678678
count: z.number(),
679679
lastSeen: z.string().datetime().nullable().optional(),
680680
firstSeen: z.string().datetime().nullable().optional(),

‎packages/mcp-core/src/tools/get-issue-tag-values.test.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { describe, it, expect } from "vitest";
2+
import { http, HttpResponse } from "msw";
3+
import { mswServer } from "@sentry/mcp-server-mocks";
24
import getIssueTagValues from "./get-issue-tag-values.js";
35
import { getServerContext } from "../test-setup.js";
46
import { UserInputError } from "../errors.js";
@@ -132,4 +134,55 @@ describe("get_issue_tag_values", () => {
132134
),
133135
).rejects.toThrow();
134136
});
137+
138+
it("handles null values in topValues gracefully", async () => {
139+
// Override the handler to return null values (which can occur with certain tag types)
140+
mswServer.use(
141+
http.get(
142+
"https://sentry.io/api/0/organizations/:org/issues/:issueId/tags/:tagKey/",
143+
() => {
144+
return HttpResponse.json({
145+
key: "custom_tag",
146+
name: "Custom Tag",
147+
totalValues: 2,
148+
topValues: [
149+
{
150+
key: "custom_tag",
151+
name: "valid_value",
152+
value: "valid_value",
153+
count: 10,
154+
lastSeen: "2024-01-15T10:30:00.000Z",
155+
firstSeen: "2024-01-10T08:00:00.000Z",
156+
},
157+
{
158+
key: "custom_tag",
159+
name: null,
160+
value: null,
161+
count: 5,
162+
lastSeen: "2024-01-14T09:00:00.000Z",
163+
firstSeen: "2024-01-12T14:00:00.000Z",
164+
},
165+
],
166+
});
167+
},
168+
),
169+
);
170+
171+
const result = await getIssueTagValues.handler(
172+
{
173+
organizationSlug: "sentry-mcp-evals",
174+
issueId: "CLOUDFLARE-MCP-41",
175+
tagKey: "custom_tag",
176+
regionUrl: null,
177+
issueUrl: undefined,
178+
},
179+
getServerContext(),
180+
);
181+
182+
// Verify the output handles null values by displaying "(null)"
183+
expect(result).toContain("# Tag Distribution: Custom Tag");
184+
expect(result).toContain("`valid_value`");
185+
expect(result).toContain("`(null)`");
186+
expect(result).toContain("| 5 |"); // The null value entry should have count 5
187+
});
135188
});

‎packages/mcp-core/src/tools/get-issue-tag-values.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,11 @@ export default defineTool({
154154
const lastSeen = value.lastSeen
155155
? new Date(value.lastSeen).toISOString().split("T")[0]
156156
: "-";
157+
// Handle null values (can occur with certain tag types)
158+
const rawValue = value.value ?? "(null)";
157159
// Truncate long values for readability
158160
let displayValue =
159-
value.value.length > 60
160-
? `${value.value.substring(0, 57)}...`
161-
: value.value;
161+
rawValue.length > 60 ? `${rawValue.substring(0, 57)}...` : rawValue;
162162
// Escape markdown table special characters (backslashes first)
163163
displayValue = displayValue
164164
.replace(/\\/g, "\\\\")

0 commit comments

Comments
 (0)