From d1714035c80800a395f4a46ae4382f4cca773aab Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:13:56 +0000 Subject: [PATCH 1/5] feat(tools): Allow eventId on get_issue_breadcrumbs Keep latest as the default while letting callers fetch breadcrumbs for a specific event within an issue. Co-Authored-By: David Cramer --- .../src/internal/tool-helpers/breadcrumbs.ts | 9 +++-- packages/mcp-core/src/skillDefinitions.json | 4 +-- packages/mcp-core/src/toolDefinitions.json | 7 +++- .../catalog/get-issue-breadcrumbs.test.ts | 35 +++++++++++++++++-- .../tools/catalog/get-issue-breadcrumbs.ts | 18 +++++++++- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/packages/mcp-core/src/internal/tool-helpers/breadcrumbs.ts b/packages/mcp-core/src/internal/tool-helpers/breadcrumbs.ts index 42aa71fd1..0bb7f8bdf 100644 --- a/packages/mcp-core/src/internal/tool-helpers/breadcrumbs.ts +++ b/packages/mcp-core/src/internal/tool-helpers/breadcrumbs.ts @@ -1,16 +1,19 @@ import type { SentryApiService } from "../../api-client/index"; /** - * Fetches breadcrumbs from the latest event for an issue and formats them. + * Fetches breadcrumbs from an issue event and formats them. + * Defaults to the latest event when eventId is omitted. */ export async function fetchAndFormatBreadcrumbs( apiService: SentryApiService, organizationSlug: string, issueId: string, + eventId: string = "latest", ): Promise { - const event = await apiService.getLatestEventForIssue({ + const event = await apiService.getEventForIssue({ organizationSlug, issueId, + eventId, }); const breadcrumbEntry = event.entries.find((e) => e.type === "breadcrumbs"); @@ -40,7 +43,7 @@ export function formatBreadcrumbs( "", `**Event ID**: ${eventId}`, "", - "No breadcrumbs found in the latest event for this issue.", + "No breadcrumbs found in this event for the issue.", ].join("\n"); } diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index 1c5cc4f7f..7b6f1c3c9 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -79,7 +79,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from the latest event for a Sentry issue.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", "requiredScopes": ["event:read"] }, { @@ -330,7 +330,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from the latest event for a Sentry issue.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", "requiredScopes": ["event:read"] }, { diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index 039a0ea42..4cabb66e0 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -2455,7 +2455,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from the latest event for a Sentry issue.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", "inputSchema": { "type": "object", "properties": { @@ -2483,6 +2483,11 @@ "type": "string", "format": "uri", "description": "The URL of the issue. e.g. https://my-organization.sentry.io/issues/PROJECT-1Z43" + }, + "eventId": { + "default": "latest", + "description": "The event ID for the issue. Defaults to `latest`.", + "type": "string" } } }, diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts index d1a3170bb..59b271c75 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts @@ -1,5 +1,5 @@ import { eventFixture, mswServer } from "@sentry/mcp-server-mocks"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { afterEach, describe, expect, it } from "vitest"; import getIssueBreadcrumbs from "./get-issue-breadcrumbs.js"; @@ -20,6 +20,7 @@ describe("get_issue_breadcrumbs", () => { organizationSlug: "sentry-mcp-evals", regionUrl: null, issueId: "CLOUDFLARE-MCP-41", + eventId: "latest", }, context, ); @@ -48,6 +49,7 @@ describe("get_issue_breadcrumbs", () => { issueUrl: "https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41/", regionUrl: null, + eventId: "latest", }, context, ); @@ -62,6 +64,7 @@ describe("get_issue_breadcrumbs", () => { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", regionUrl: null, + eventId: "latest", }, { ...context, @@ -96,12 +99,40 @@ describe("get_issue_breadcrumbs", () => { organizationSlug: "sentry-mcp-evals", regionUrl: null, issueId: "CLOUDFLARE-MCP-41", + eventId: "latest", }, context, ); expect(result).toContain( - "No breadcrumbs found in the latest event for this issue.", + "No breadcrumbs found in this event for the issue.", ); }); + + it("returns breadcrumbs for an explicit event ID", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/explicit-event-id/", + () => + HttpResponse.json({ + ...eventFixture, + id: "explicit-event-id", + }), + { once: true }, + ), + ); + + const result = await getIssueBreadcrumbs.handler( + { + organizationSlug: "sentry-mcp-evals", + regionUrl: null, + issueId: "CLOUDFLARE-MCP-41", + eventId: "explicit-event-id", + }, + context, + ); + + expect(result).toContain("# Breadcrumbs for CLOUDFLARE-MCP-41"); + expect(result).toContain("**Event ID**: explicit-event-id"); + }); }); diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts index 8984b7379..e1b9ed308 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts @@ -1,4 +1,5 @@ import { setTag } from "@sentry/core"; +import { z } from "zod"; import { ApiNotFoundError } from "../../api-client"; import { apiServiceFromContext } from "../../internal/tool-helpers/api"; import { fetchAndFormatBreadcrumbs } from "../../internal/tool-helpers/breadcrumbs"; @@ -21,23 +22,36 @@ export default defineTool({ skills: ["inspect", "triage"], requiredScopes: ["event:read"], description: [ - "Get the breadcrumb trail from the latest event for a Sentry issue.", + "Get the breadcrumb trail from a Sentry issue event.", + "Defaults to the latest event when eventId is omitted.", "", "Use this tool when you need to:", "- See the user and application actions leading up to an error", "- Inspect navigation, console, HTTP, and other breadcrumb events", "- Reconstruct the immediate context before an issue occurred", + "- Compare breadcrumb trails across multiple events in the same issue", "", "", "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')", + "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')", "get_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')", "", + "", + "", + "- `eventId` is optional and defaults to `latest`.", + "- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.", + "", ].join("\n"), inputSchema: { organizationSlug: ParamOrganizationSlug.optional(), regionUrl: ParamRegionUrl.nullable().default(null), issueId: ParamIssueShortId.optional(), issueUrl: ParamIssueUrl.optional(), + eventId: z + .string() + .trim() + .default("latest") + .describe("The event ID for the issue. Defaults to `latest`."), }, annotations: { readOnlyHint: true, @@ -69,12 +83,14 @@ export default defineTool({ apiService, parsed.organizationSlug, parsed.issueId, + params.eventId, ); } catch (error) { if (error instanceof ApiNotFoundError) { throw enhanceNotFoundError(error, { organizationSlug: parsed.organizationSlug, issueId: parsed.issueId, + eventId: params.eventId, }); } throw error; From 62415fe59d689091348c7deae968d0dd2e9ac966 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:28:43 +0000 Subject: [PATCH 2/5] fix(tools): Reject empty eventId on issue breadcrumbs Require a non-empty eventId so whitespace-only values fail validation instead of requesting /events//. --- packages/mcp-core/src/toolDefinitions.json | 3 ++- packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index 4cabb66e0..cc4fe7107 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -2487,7 +2487,8 @@ "eventId": { "default": "latest", "description": "The event ID for the issue. Defaults to `latest`.", - "type": "string" + "type": "string", + "minLength": 1 } } }, diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts index e1b9ed308..f2b10ab3e 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts @@ -50,6 +50,7 @@ export default defineTool({ eventId: z .string() .trim() + .min(1) .default("latest") .describe("The event ID for the issue. Defaults to `latest`."), }, From 6dc34012f234a4e89760f0781725ccbcba200412 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:39:33 +0000 Subject: [PATCH 3/5] docs(tools): Drop duplicated eventId hints Remove the get_issue_breadcrumbs hints block that restated the default and eventId behavior already covered by the description and schema. --- packages/mcp-core/src/skillDefinitions.json | 4 ++-- packages/mcp-core/src/toolDefinitions.json | 2 +- packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index 7b6f1c3c9..b9e57795c 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -79,7 +79,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "requiredScopes": ["event:read"] }, { @@ -330,7 +330,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "requiredScopes": ["event:read"] }, { diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index cc4fe7107..b53cef998 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -2455,7 +2455,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n\n\n\n- `eventId` is optional and defaults to `latest`.\n- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "inputSchema": { "type": "object", "properties": { diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts index f2b10ab3e..3e1e14c55 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts @@ -36,11 +36,6 @@ export default defineTool({ "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')", "get_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')", "", - "", - "", - "- `eventId` is optional and defaults to `latest`.", - "- Pass a specific event ID to inspect breadcrumbs for a non-latest event in the issue.", - "", ].join("\n"), inputSchema: { organizationSlug: ParamOrganizationSlug.optional(), From a6bf02ae8cc21eb7deb7c129bf1d74ece95ddffa Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:50:59 +0000 Subject: [PATCH 4/5] fix(api): Encode event path segments in getEventForIssue Prevent path traversal through unencoded eventId (and related segments) when looking up issue events after project-constraint checks. --- packages/mcp-core/src/api-client/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts index c067f5afc..6c503c45b 100644 --- a/packages/mcp-core/src/api-client/client.ts +++ b/packages/mcp-core/src/api-client/client.ts @@ -3259,7 +3259,7 @@ export class SentryApiService { opts?: RequestOptions, ): Promise { const body = await this.requestJSON( - `/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/`, + `/organizations/${encodeURIComponent(organizationSlug)}/issues/${encodeURIComponent(issueId)}/events/${encodeURIComponent(eventId)}/`, undefined, opts, ); From dd745c965b55453a70d03adb992c0ec5d57252d0 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:19:10 +0000 Subject: [PATCH 5/5] fix(schema): Constrain event IDs and allow latest via union Tighten ParamEventId to 32-char hex and add ParamEventIdOrLatest so issue-scoped tools accept either a real event id or latest by default. Co-Authored-By: David Cramer --- packages/mcp-core/src/schema.ts | 18 ++++++++- packages/mcp-core/src/skillDefinitions.json | 10 ++--- packages/mcp-core/src/toolDefinitions.json | 39 ++++++++++++++----- .../catalog/get-event-attachment.test.ts | 4 +- .../src/tools/catalog/get-event-stacktrace.ts | 9 ++--- .../catalog/get-issue-breadcrumbs.test.ts | 8 ++-- .../tools/catalog/get-issue-breadcrumbs.ts | 11 ++---- .../src/tools/catalog/get-issue-details.ts | 6 +-- packages/mcp-server-mocks/src/index.ts | 4 +- 9 files changed, 69 insertions(+), 40 deletions(-) diff --git a/packages/mcp-core/src/schema.ts b/packages/mcp-core/src/schema.ts index d14305b7f..f856f9483 100644 --- a/packages/mcp-core/src/schema.ts +++ b/packages/mcp-core/src/schema.ts @@ -237,7 +237,23 @@ export const ParamSentryGuide = z "Use either a platform (e.g., 'javascript', 'python') or platform/guide combination (e.g., 'javascript/nextjs', 'python/django').", ); -export const ParamEventId = z.string().trim().describe("The ID of the event."); +export const ParamEventId = z + .string() + .trim() + .regex( + /^[0-9a-fA-F]{32}$/, + "Event ID must be a 32-character hexadecimal string", + ) + .describe("The ID of the event. e.g. `c49541c747cb4d8aa3efb70ca5aba243`"); + +/** + * Issue-scoped event selector used by tools that can target either a concrete + * event or the issue's latest event. + */ +export const ParamEventIdOrLatest = z + .union([z.literal("latest"), ParamEventId]) + .default("latest") + .describe("The event ID for the issue, or `latest`. Defaults to `latest`."); export const ParamAttachmentId = z .string() diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index b9e57795c..8f4eed553 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -69,7 +69,7 @@ }, { "name": "get_event_stacktrace", - "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", + "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", "requiredScopes": ["event:read"] }, { @@ -79,7 +79,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "requiredScopes": ["event:read"] }, { @@ -214,7 +214,7 @@ }, { "name": "get_event_stacktrace", - "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", + "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", "requiredScopes": ["event:read"] }, { @@ -320,7 +320,7 @@ }, { "name": "get_event_stacktrace", - "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", + "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", "requiredScopes": ["event:read"] }, { @@ -330,7 +330,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "requiredScopes": ["event:read"] }, { diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index b53cef998..2770a2d38 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -2321,7 +2321,8 @@ }, "eventId": { "type": "string", - "description": "The ID of the event." + "pattern": "^[0-9a-fA-F]{32}$", + "description": "The ID of the event. e.g. `c49541c747cb4d8aa3efb70ca5aba243`" }, "attachmentId": { "default": null, @@ -2356,7 +2357,7 @@ }, { "name": "get_event_stacktrace", - "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", + "description": "Get a full thread stacktrace from a specific Sentry event.\n\nUse this tool when you need to:\n- Fetch the full stacktrace for a thread listed in issue details\n- Inspect a non-crashed thread from an event with multiple threads\n- Get Sentry's default selected thread stacktrace when no thread is specified\n\n\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243', thread=259)\nget_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')\n\n\n\n- `thread` is optional. If omitted, this returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.\n- Pass `thread` as a numeric Thread ID or exact thread Name from the issue details thread list.\n- If the issue details show only one useful thread, omit `thread`.\n", "inputSchema": { "type": "object", "properties": { @@ -2382,8 +2383,18 @@ }, "eventId": { "default": "latest", - "description": "The event ID for the issue. Defaults to `latest`.", - "type": "string" + "description": "The event ID for the issue, or `latest`. Defaults to `latest`.", + "anyOf": [ + { + "type": "string", + "const": "latest" + }, + { + "type": "string", + "pattern": "^[0-9a-fA-F]{32}$", + "description": "The ID of the event. e.g. `c49541c747cb4d8aa3efb70ca5aba243`" + } + ] }, "thread": { "description": "Optional thread selector. Pass a numeric thread ID, or an exact thread name string. If omitted, returns the same default thread Sentry selects: first crashed thread, then first thread with a stacktrace, then first thread.", @@ -2455,7 +2466,7 @@ }, { "name": "get_issue_breadcrumbs", - "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", + "description": "Get the breadcrumb trail from a Sentry issue event.\nDefaults to the latest event when eventId is omitted.\n\nUse this tool when you need to:\n- See the user and application actions leading up to an error\n- Inspect navigation, console, HTTP, and other breadcrumb events\n- Reconstruct the immediate context before an issue occurred\n- Compare breadcrumb trails across multiple events in the same issue\n\n\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')\nget_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243')\nget_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')\n", "inputSchema": { "type": "object", "properties": { @@ -2486,9 +2497,18 @@ }, "eventId": { "default": "latest", - "description": "The event ID for the issue. Defaults to `latest`.", - "type": "string", - "minLength": 1 + "description": "The event ID for the issue, or `latest`. Defaults to `latest`.", + "anyOf": [ + { + "type": "string", + "const": "latest" + }, + { + "type": "string", + "pattern": "^[0-9a-fA-F]{32}$", + "description": "The ID of the event. e.g. `c49541c747cb4d8aa3efb70ca5aba243`" + } + ] } } }, @@ -2524,7 +2544,8 @@ }, "eventId": { "type": "string", - "description": "The ID of the event." + "pattern": "^[0-9a-fA-F]{32}$", + "description": "The ID of the event. e.g. `c49541c747cb4d8aa3efb70ca5aba243`" }, "issueUrl": { "type": "string", diff --git a/packages/mcp-core/src/tools/catalog/get-event-attachment.test.ts b/packages/mcp-core/src/tools/catalog/get-event-attachment.test.ts index 18485a467..b1b9b9ebe 100644 --- a/packages/mcp-core/src/tools/catalog/get-event-attachment.test.ts +++ b/packages/mcp-core/src/tools/catalog/get-event-attachment.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import getEventAttachment from "./get-event-attachment.js"; describe("get_event_attachment", () => { @@ -110,7 +110,7 @@ describe("get_event_attachment", () => { { organizationSlug: "sentry-mcp-evals", projectSlug: "cloudflare-mcp", - eventId: "octet-stream-event-id", + eventId: "d49541c747cb4d8aa3efb70ca5aba244", attachmentId: "456", regionUrl: null, }, diff --git a/packages/mcp-core/src/tools/catalog/get-event-stacktrace.ts b/packages/mcp-core/src/tools/catalog/get-event-stacktrace.ts index 897414755..6e8f4e53e 100644 --- a/packages/mcp-core/src/tools/catalog/get-event-stacktrace.ts +++ b/packages/mcp-core/src/tools/catalog/get-event-stacktrace.ts @@ -6,6 +6,7 @@ import { defineTool } from "../../internal/tool-helpers/define"; import { enhanceNotFoundError } from "../../internal/tool-helpers/enhance-error"; import { ensureIssueWithinProjectConstraint } from "../../internal/tool-helpers/issue"; import { + ParamEventIdOrLatest, ParamIssueShortId, ParamOrganizationSlug, ParamRegionUrl, @@ -27,7 +28,7 @@ export default defineTool({ "", "", "get_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123')", - "get_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123', thread=259)", + "get_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243', thread=259)", "get_event_stacktrace(organizationSlug='my-org', issueId='PROJECT-123', thread='main')", "", "", @@ -41,11 +42,7 @@ export default defineTool({ organizationSlug: ParamOrganizationSlug, regionUrl: ParamRegionUrl.nullable().default(null), issueId: ParamIssueShortId, - eventId: z - .string() - .trim() - .default("latest") - .describe("The event ID for the issue. Defaults to `latest`."), + eventId: ParamEventIdOrLatest, thread: z .union([z.number().int(), z.string().trim().min(1)]) .optional() diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts index 59b271c75..61f78842e 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.test.ts @@ -112,11 +112,11 @@ describe("get_issue_breadcrumbs", () => { it("returns breadcrumbs for an explicit event ID", async () => { mswServer.use( http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/explicit-event-id/", + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/c49541c747cb4d8aa3efb70ca5aba243/", () => HttpResponse.json({ ...eventFixture, - id: "explicit-event-id", + id: "c49541c747cb4d8aa3efb70ca5aba243", }), { once: true }, ), @@ -127,12 +127,12 @@ describe("get_issue_breadcrumbs", () => { organizationSlug: "sentry-mcp-evals", regionUrl: null, issueId: "CLOUDFLARE-MCP-41", - eventId: "explicit-event-id", + eventId: "c49541c747cb4d8aa3efb70ca5aba243", }, context, ); expect(result).toContain("# Breadcrumbs for CLOUDFLARE-MCP-41"); - expect(result).toContain("**Event ID**: explicit-event-id"); + expect(result).toContain("**Event ID**: c49541c747cb4d8aa3efb70ca5aba243"); }); }); diff --git a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts index 3e1e14c55..7bc49cbee 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-breadcrumbs.ts @@ -1,5 +1,4 @@ import { setTag } from "@sentry/core"; -import { z } from "zod"; import { ApiNotFoundError } from "../../api-client"; import { apiServiceFromContext } from "../../internal/tool-helpers/api"; import { fetchAndFormatBreadcrumbs } from "../../internal/tool-helpers/breadcrumbs"; @@ -10,6 +9,7 @@ import { parseIssueParams, } from "../../internal/tool-helpers/issue"; import { + ParamEventIdOrLatest, ParamIssueShortId, ParamIssueUrl, ParamOrganizationSlug, @@ -33,7 +33,7 @@ export default defineTool({ "", "", "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123')", - "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='abc123')", + "get_issue_breadcrumbs(organizationSlug='my-org', issueId='PROJECT-123', eventId='c49541c747cb4d8aa3efb70ca5aba243')", "get_issue_breadcrumbs(issueUrl='https://my-org.sentry.io/issues/PROJECT-123/')", "", ].join("\n"), @@ -42,12 +42,7 @@ export default defineTool({ regionUrl: ParamRegionUrl.nullable().default(null), issueId: ParamIssueShortId.optional(), issueUrl: ParamIssueUrl.optional(), - eventId: z - .string() - .trim() - .min(1) - .default("latest") - .describe("The event ID for the issue. Defaults to `latest`."), + eventId: ParamEventIdOrLatest, }, annotations: { readOnlyHint: true, diff --git a/packages/mcp-core/src/tools/catalog/get-issue-details.ts b/packages/mcp-core/src/tools/catalog/get-issue-details.ts index 1060db499..d2f069de4 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-details.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-details.ts @@ -1,7 +1,6 @@ import { setTag } from "@sentry/core"; -import { z } from "zod"; -import { ApiNotFoundError } from "../../api-client"; import type { SentryApiService } from "../../api-client"; +import { ApiNotFoundError } from "../../api-client"; import type { AutofixRunState, DefaultEvent, @@ -27,6 +26,7 @@ import { parseIssueParams, } from "../../internal/tool-helpers/issue"; import { + ParamEventId, ParamIssueShortId, ParamIssueUrl, ParamOrganizationSlug, @@ -88,7 +88,7 @@ export default defineTool({ organizationSlug: ParamOrganizationSlug.optional(), regionUrl: ParamRegionUrl.nullable().default(null), issueId: ParamIssueShortId.optional(), - eventId: z.string().trim().describe("The ID of the event.").optional(), + eventId: ParamEventId.optional(), issueUrl: ParamIssueUrl.optional(), }, annotations: { diff --git a/packages/mcp-server-mocks/src/index.ts b/packages/mcp-server-mocks/src/index.ts index 64a5c5aed..1794b834c 100644 --- a/packages/mcp-server-mocks/src/index.ts +++ b/packages/mcp-server-mocks/src/index.ts @@ -1507,7 +1507,7 @@ export const restHandlers = buildHandlers([ // Step 1 (metadata), so the attachment is rendered as an image not a blob. { method: "get", - path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/octet-stream-event-id/attachments/", + path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/d49541c747cb4d8aa3efb70ca5aba244/attachments/", fetch: () => HttpResponse.json([ { @@ -1524,7 +1524,7 @@ export const restHandlers = buildHandlers([ }, { method: "get", - path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/octet-stream-event-id/attachments/456/", + path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/d49541c747cb4d8aa3efb70ca5aba244/attachments/456/", fetch: () => { const mockBlob = new Blob(["fake image data"], { type: "image/png" }); return new HttpResponse(mockBlob, {