From 467ca50a5938a2e8790b204bacf8330c83425dab Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Thu, 16 Jul 2026 15:07:38 -0700 Subject: [PATCH 1/4] mcp --- packages/mcp-core/src/api-client/client.ts | 3 +- packages/mcp-core/src/api-client/schema.ts | 1 + packages/mcp-core/src/internal/formatting.ts | 32 ++-- .../tools/catalog/get-issue-details.test.ts | 147 ++++++++++++++++++ 4 files changed, 170 insertions(+), 13 deletions(-) diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts index 7eb00f834..354f34705 100644 --- a/packages/mcp-core/src/api-client/client.ts +++ b/packages/mcp-core/src/api-client/client.ts @@ -3166,7 +3166,6 @@ export class SentryApiService { ); return ExternalIssueListSchema.parse(body); } - /** * Retrieves issue user reports and returns the next Sentry cursor when another page exists. */ @@ -3218,7 +3217,7 @@ export class SentryApiService { opts?: RequestOptions, ): Promise { const body = await this.requestJSON( - `/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/`, + `/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/?llmFormat=markdown`, // send the param undefined, opts, ); diff --git a/packages/mcp-core/src/api-client/schema.ts b/packages/mcp-core/src/api-client/schema.ts index 9c6e54913..3ff87d036 100644 --- a/packages/mcp-core/src/api-client/schema.ts +++ b/packages/mcp-core/src/api-client/schema.ts @@ -1011,6 +1011,7 @@ const BaseEventSchema = z.object({ _meta: z.unknown().optional(), // dateReceived is when the server received the event (may not be present in all contexts) dateReceived: z.string().datetime().optional(), + formatted: z.object({ format: z.string(), content: z.string() }).optional(), // add to BaseEventSchema }); export const ErrorEventSchema = BaseEventSchema.omit({ diff --git a/packages/mcp-core/src/internal/formatting.ts b/packages/mcp-core/src/internal/formatting.ts index 1e7e268bc..8c627439b 100644 --- a/packages/mcp-core/src/internal/formatting.ts +++ b/packages/mcp-core/src/internal/formatting.ts @@ -2147,17 +2147,27 @@ export function formatIssueOutput({ output += `**Message**:\n${event.message}\n`; } output += "\n"; - output += formatEventOutput(event, { - performanceTrace, - replaySummary: { - apiService, - organizationSlug, - relatedReplayIds, - experimentalMode: experimentalMode ?? false, - availableToolNames, - directToolNames, - }, - }); + if ( + (event.type === "error" || + event.type === "default" || + event.type === "generic" || + event.type === "csp") && + event.formatted?.content + ) { + output += event.formatted.content; + } else { + output += formatEventOutput(event, { + performanceTrace, + replaySummary: { + apiService, + organizationSlug, + relatedReplayIds, + experimentalMode: experimentalMode ?? false, + availableToolNames, + directToolNames, + }, + }); + } // Add Seer context if available if (autofixState) { 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 aefeb65fe..140c90f48 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 @@ -296,6 +296,153 @@ describe("get_issue_details", () => { expect(result).not.toContain("**Culprit**: null"); }); + it("uses the shared formatter's formatted.content for error/default events", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/", + () => + HttpResponse.json({ + ...createDefaultEvent(), + formatted: { + format: "markdown", + content: "## Title\n\nSHARED-FORMATTER-MARKER", + }, + }), + { once: true }, + ), + ); + + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "CLOUDFLARE-MCP-41", + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + // error/default events render their body from the shared formatter's content + expect(result).toContain("SHARED-FORMATTER-MARKER"); + }); + + it("ignores formatted.content for non-error events (transaction)", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/", + () => HttpResponse.json(createPerformanceIssue()), + { once: true }, + ), + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/", + () => + HttpResponse.json({ + ...createPerformanceEvent(), + formatted: { + format: "markdown", + content: "TRANSACTION-SHOULD-IGNORE-THIS", + }, + }), + { once: true }, + ), + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/trace/abcdef1234567890abcdef1234567890/", + () => HttpResponse.json(createTraceResponseFixture()), + { once: true }, + ), + ); + + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "PERF-N1-001", + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + // transaction events still route through formatEventOutput, so formatted is unused + expect(result).toContain("Issue PERF-N1-001"); // sanity: real output was produced + expect(result).not.toContain("TRANSACTION-SHOULD-IGNORE-THIS"); + }); + + it("uses formatted.content for generic events", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/", + () => HttpResponse.json(createRegressedIssue()), + { once: true }, + ), + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/events/latest/", + () => + HttpResponse.json({ + ...createGenericEvent(), + formatted: { + format: "markdown", + content: "## Evidence\n\nGENERIC-FORMATTER-MARKER", + }, + }), + { once: true }, + ), + ); + + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "MCP-SERVER-EQE", + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + expect(result).toContain("GENERIC-FORMATTER-MARKER"); + // the shared formatter replaces MCP's generic renderer + expect(result).not.toContain("### Performance Regression Details"); + }); + + it("uses formatted.content for csp events", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/", + () => HttpResponse.json(createCspIssue()), + { once: true }, + ), + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/events/latest/", + () => + HttpResponse.json({ + ...createCspEvent(), + formatted: { + format: "markdown", + content: "## CSP\n\nCSP-FORMATTER-MARKER", + }, + }), + { once: true }, + ), + ); + + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "BLOG-CSP-4XC", + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + expect(result).toContain("CSP-FORMATTER-MARKER"); + // the shared formatter replaces MCP's CSP renderer + expect(result).not.toContain("### CSP Violation"); + }); + it("surfaces AI conversation IDs found by bounded span lookup", async () => { const traceId = "11112222333344445555666677778888"; const event = createDefaultEvent({ From c145b41fdbd582fe2cd8d87f2255b69b5cf55156 Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Thu, 16 Jul 2026 16:05:49 -0700 Subject: [PATCH 2/4] mcp --- packages/mcp-core/src/api-client/client.ts | 2 +- packages/mcp-core/src/api-client/schema.ts | 2 + packages/mcp-core/src/internal/formatting.ts | 10 +++++ .../catalog/analyze-issue-with-seer.test.ts | 34 +++++++++++++++++ .../tools/catalog/analyze-issue-with-seer.ts | 12 ++++-- .../tools/catalog/get-issue-details.test.ts | 37 +++++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts index 354f34705..5c7fb2cbb 100644 --- a/packages/mcp-core/src/api-client/client.ts +++ b/packages/mcp-core/src/api-client/client.ts @@ -3982,7 +3982,7 @@ export class SentryApiService { opts?: RequestOptions, ): Promise { const body = await this.requestJSON( - `/organizations/${organizationSlug}/issues/${issueId}/autofix/`, + `/organizations/${organizationSlug}/issues/${issueId}/autofix/?llmFormat=markdown`, undefined, opts, ); diff --git a/packages/mcp-core/src/api-client/schema.ts b/packages/mcp-core/src/api-client/schema.ts index 3ff87d036..0c5536733 100644 --- a/packages/mcp-core/src/api-client/schema.ts +++ b/packages/mcp-core/src/api-client/schema.ts @@ -1232,6 +1232,8 @@ export const AutofixRunStateSchema = z.object({ }) .passthrough() .nullable(), + // shared-formatter output, present when the autofix endpoint is called with ?llmFormat + formatted: z.object({ format: z.string(), content: z.string() }).optional(), }); export const EventAttachmentSchema = z.object({ diff --git a/packages/mcp-core/src/internal/formatting.ts b/packages/mcp-core/src/internal/formatting.ts index 8c627439b..24549a2b3 100644 --- a/packages/mcp-core/src/internal/formatting.ts +++ b/packages/mcp-core/src/internal/formatting.ts @@ -2154,6 +2154,16 @@ export function formatIssueOutput({ event.type === "csp") && event.formatted?.content ) { + // the shared formatter body doesn't include the replay note — add it here to match formatEventOutput + output += formatIssueReplayOutput({ + apiService, + organizationSlug, + event, + relatedReplayIds, + experimentalMode: experimentalMode ?? false, + availableToolNames, + directToolNames, + }); output += event.formatted.content; } else { output += formatEventOutput(event, { diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts index dbae16652..af68aacbd 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts @@ -57,6 +57,40 @@ describe("analyze_issue_with_seer", () => { expect(result).toContain("The analysis has completed successfully."); }); + it("uses formatted.content from the autofix endpoint when present", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-FMT/autofix/", + () => + HttpResponse.json({ + autofix: { run_id: 42, status: "completed", blocks: [] }, + formatted: { + format: "markdown", + content: "## Root Cause\n\nSHARED-AUTOFIX-MARKER", + }, + }), + ), + ); + + const result = await analyzeIssueWithSeer.handler( + { + organizationSlug: "sentry-mcp-evals", + regionUrl: null, + instruction: undefined, + issueId: "CLOUDFLARE-MCP-FMT", + issueUrl: undefined, + }, + { + constraints: { organizationSlug: undefined }, + accessToken: "access-token", + userId: "1", + }, + ); + + expect(result).toContain("SHARED-AUTOFIX-MARKER"); // body from the shared /autofix/ formatter + expect(result).not.toContain(" { mswServer.use( http.get( diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts index 6dfbe4488..6a8ac3ed6 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts @@ -177,7 +177,9 @@ export default defineTool({ if (isTerminalStatus(existingStatus)) { // Return results immediately, no polling needed output += `## Analysis ${getStatusDisplayName(existingStatus)}\n\n`; - output += getOutputForAutofixRun(autofixState.autofix); + output += + autofixState.formatted?.content ?? + getOutputForAutofixRun(autofixState.autofix); if (existingStatus !== "completed") { output += `\n**Status**: ${existingStatus}\n`; @@ -210,7 +212,9 @@ export default defineTool({ // Check if completed (terminal state) if (isTerminalStatus(status)) { output += `## Analysis ${getStatusDisplayName(status)}\n\n`; - output += getOutputForAutofixRun(autofixState.autofix); + output += + autofixState.formatted?.content ?? + getOutputForAutofixRun(autofixState.autofix); if (status !== "completed") { output += `\n**Status**: ${status}\n`; @@ -279,7 +283,9 @@ export default defineTool({ // Show current progress if (autofixState.autofix) { output += `**Current Status**: ${getStatusDisplayName(autofixState.autofix.status)}\n\n`; - output += getOutputForAutofixRun(autofixState.autofix); + output += + autofixState.formatted?.content ?? + getOutputForAutofixRun(autofixState.autofix); } // Timeout reached 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 140c90f48..0162c2470 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 @@ -443,6 +443,43 @@ describe("get_issue_details", () => { expect(result).not.toContain("### CSP Violation"); }); + it("keeps the replay note when error events use formatted.content", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/", + () => + HttpResponse.json({ + ...createDefaultEvent(), + contexts: { + replay: { + type: "default", + replay_id: "1234567890abcdef1234567890abcdef", + }, + }, + formatted: { + format: "markdown", + content: "## Title\n\nBODY-FROM-FORMATTER", + }, + }), + { 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("BODY-FROM-FORMATTER"); // body from the shared formatter + expect(result).toContain("## Session Replay"); // replay note preserved (was inside formatEventOutput) + }); + it("surfaces AI conversation IDs found by bounded span lookup", async () => { const traceId = "11112222333344445555666677778888"; const event = createDefaultEvent({ From 52589f5e740d020f60545fb057b95a7e25d3e0e1 Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Mon, 20 Jul 2026 12:06:15 -0700 Subject: [PATCH 3/4] clean up --- packages/mcp-core/src/api-client/client.ts | 3 +- packages/mcp-core/src/api-client/schema.ts | 3 +- packages/mcp-core/src/internal/formatting.ts | 25 ++- .../src/internal/tool-helpers/seer.ts | 14 ++ .../catalog/analyze-issue-with-seer.test.ts | 4 +- .../tools/catalog/analyze-issue-with-seer.ts | 28 ++- .../tools/catalog/get-issue-details.test.ts | 177 +++++++++--------- 7 files changed, 143 insertions(+), 111 deletions(-) diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts index 5c7fb2cbb..76d036344 100644 --- a/packages/mcp-core/src/api-client/client.ts +++ b/packages/mcp-core/src/api-client/client.ts @@ -3166,6 +3166,7 @@ export class SentryApiService { ); return ExternalIssueListSchema.parse(body); } + /** * Retrieves issue user reports and returns the next Sentry cursor when another page exists. */ @@ -3217,7 +3218,7 @@ export class SentryApiService { opts?: RequestOptions, ): Promise { const body = await this.requestJSON( - `/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/?llmFormat=markdown`, // send the param + `/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/?llmFormat=markdown`, undefined, opts, ); diff --git a/packages/mcp-core/src/api-client/schema.ts b/packages/mcp-core/src/api-client/schema.ts index 0c5536733..daa6e2976 100644 --- a/packages/mcp-core/src/api-client/schema.ts +++ b/packages/mcp-core/src/api-client/schema.ts @@ -1011,7 +1011,8 @@ const BaseEventSchema = z.object({ _meta: z.unknown().optional(), // dateReceived is when the server received the event (may not be present in all contexts) dateReceived: z.string().datetime().optional(), - formatted: z.object({ format: z.string(), content: z.string() }).optional(), // add to BaseEventSchema + // shared-formatter output, present when the event endpoint is called with ?llmFormat + formatted: z.object({ format: z.string(), content: z.string() }).optional(), }); export const ErrorEventSchema = BaseEventSchema.omit({ diff --git a/packages/mcp-core/src/internal/formatting.ts b/packages/mcp-core/src/internal/formatting.ts index 24549a2b3..1ba718552 100644 --- a/packages/mcp-core/src/internal/formatting.ts +++ b/packages/mcp-core/src/internal/formatting.ts @@ -33,6 +33,7 @@ import { getAutofixArtifactSummaries, getStatusDisplayName, isTerminalStatus, + wrapSeerContent, } from "./tool-helpers/seer"; import { formatToolCallInstruction } from "./tool-helpers/tool-call-formatting"; import { @@ -1926,6 +1927,16 @@ function formatSeerSummary(autofixState: AutofixRunState | undefined): string { return ""; } + // Prefer the shared formatter's analysis when the endpoint provides it. + // Seer content is LLM-generated, so wrap it in the untrusted-data boundary. + if (autofixState.formatted?.content) { + const wrapped = wrapSeerContent( + autofixState.formatted.content, + autofixState.autofix.run_id, + ); + return `## Seer Analysis\n\n${wrapped}\n`; + } + const { autofix } = autofixState; const parts: string[] = []; @@ -2128,12 +2139,12 @@ export function formatIssueOutput({ // "default" type represents error events without exception data // "generic" type represents performance regressions and metric-based issues // "csp" type represents Content Security Policy violations - if ( + const isSharedFormatterType = event.type === "error" || event.type === "default" || event.type === "generic" || - event.type === "csp" - ) { + event.type === "csp"; + if (isSharedFormatterType) { const typedEvent = event as | z.infer | z.infer @@ -2147,13 +2158,7 @@ export function formatIssueOutput({ output += `**Message**:\n${event.message}\n`; } output += "\n"; - if ( - (event.type === "error" || - event.type === "default" || - event.type === "generic" || - event.type === "csp") && - event.formatted?.content - ) { + if (isSharedFormatterType && event.formatted?.content) { // the shared formatter body doesn't include the replay note — add it here to match formatEventOutput output += formatIssueReplayOutput({ apiService, diff --git a/packages/mcp-core/src/internal/tool-helpers/seer.ts b/packages/mcp-core/src/internal/tool-helpers/seer.ts index be03315bd..da65ae4dd 100644 --- a/packages/mcp-core/src/internal/tool-helpers/seer.ts +++ b/packages/mcp-core/src/internal/tool-helpers/seer.ts @@ -116,6 +116,20 @@ function wrapSeerAnalysisOutput({ return `\n${output.trimEnd()}\n\n`; } +/** + * Wraps shared-formatter Seer analysis content in the provenance boundary, + * mirroring the tags getOutputForAutofixRun applies to MCP-rendered output. + * Seer content is LLM-generated, so it must be marked as untrusted data. + */ +export function wrapSeerContent(content: string, runId?: number): string { + return wrapSeerAnalysisOutput({ + output: content, + runId, + step: "analysis", + includeProvenanceTags: true, + }); +} + // Artifact data shapes from getsentry/sentry's // `src/sentry/seer/autofix/artifact_schemas.py`. Fields are LLM-generated, so // everything is treated as optional. diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts index af68aacbd..3c99e606e 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts @@ -88,7 +88,9 @@ describe("analyze_issue_with_seer", () => { ); expect(result).toContain("SHARED-AUTOFIX-MARKER"); // body from the shared /autofix/ formatter - expect(result).not.toContain("'); + expect(result).toContain(""); }); it("wraps completed Seer-authored sections with provenance tags", async () => { diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts index 6a8ac3ed6..68c60ba64 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts @@ -11,6 +11,7 @@ import { isTerminalStatus, getHumanInterventionGuidance, getOutputForAutofixRun, + wrapSeerContent, getActiveAutofixTodo, getSeerUnsupportedIssueMessage, isSeerSupportedIssue, @@ -177,9 +178,12 @@ export default defineTool({ if (isTerminalStatus(existingStatus)) { // Return results immediately, no polling needed output += `## Analysis ${getStatusDisplayName(existingStatus)}\n\n`; - output += - autofixState.formatted?.content ?? - getOutputForAutofixRun(autofixState.autofix); + output += autofixState.formatted?.content + ? wrapSeerContent( + autofixState.formatted.content, + autofixState.autofix.run_id, + ) + : getOutputForAutofixRun(autofixState.autofix); if (existingStatus !== "completed") { output += `\n**Status**: ${existingStatus}\n`; @@ -212,9 +216,12 @@ export default defineTool({ // Check if completed (terminal state) if (isTerminalStatus(status)) { output += `## Analysis ${getStatusDisplayName(status)}\n\n`; - output += - autofixState.formatted?.content ?? - getOutputForAutofixRun(autofixState.autofix); + output += autofixState.formatted?.content + ? wrapSeerContent( + autofixState.formatted.content, + autofixState.autofix.run_id, + ) + : getOutputForAutofixRun(autofixState.autofix); if (status !== "completed") { output += `\n**Status**: ${status}\n`; @@ -283,9 +290,12 @@ export default defineTool({ // Show current progress if (autofixState.autofix) { output += `**Current Status**: ${getStatusDisplayName(autofixState.autofix.status)}\n\n`; - output += - autofixState.formatted?.content ?? - getOutputForAutofixRun(autofixState.autofix); + output += autofixState.formatted?.content + ? wrapSeerContent( + autofixState.formatted.content, + autofixState.autofix.run_id, + ) + : getOutputForAutofixRun(autofixState.autofix); } // Timeout reached 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 0162c2470..61a713a18 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 @@ -296,36 +296,76 @@ describe("get_issue_details", () => { expect(result).not.toContain("**Culprit**: null"); }); - it("uses the shared formatter's formatted.content for error/default events", async () => { - mswServer.use( - http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/", - () => - HttpResponse.json({ - ...createDefaultEvent(), - formatted: { - format: "markdown", - content: "## Title\n\nSHARED-FORMATTER-MARKER", - }, + it.each([ + { + type: "error/default", + issueId: "CLOUDFLARE-MCP-41", + issue: undefined, + event: createDefaultEvent, + marker: "SHARED-FORMATTER-MARKER", + replacedRenderer: undefined, + }, + { + type: "generic", + issueId: "MCP-SERVER-EQE", + issue: createRegressedIssue, + event: createGenericEvent, + marker: "GENERIC-FORMATTER-MARKER", + replacedRenderer: "### Performance Regression Details", + }, + { + type: "csp", + issueId: "BLOG-CSP-4XC", + issue: createCspIssue, + event: createCspEvent, + marker: "CSP-FORMATTER-MARKER", + replacedRenderer: "### CSP Violation", + }, + ])( + "uses formatted.content for $type events", + async ({ issueId, issue, event, marker, replacedRenderer }) => { + const base = `https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/${issueId}`; + if (issue) { + mswServer.use( + http.get(`${base}/`, () => HttpResponse.json(issue()), { + once: true, }), - { once: true }, - ), - ); - - const result = await getIssueDetails.handler( - { - organizationSlug: "sentry-mcp-evals", - issueId: "CLOUDFLARE-MCP-41", - eventId: undefined, - issueUrl: undefined, - regionUrl: null, - }, - baseContext, - ); + ); + } + mswServer.use( + http.get( + `${base}/events/latest/`, + () => + HttpResponse.json({ + ...event(), + formatted: { + format: "markdown", + content: `## Body\n\n${marker}`, + }, + }), + { once: true }, + ), + ); - // error/default events render their body from the shared formatter's content - expect(result).toContain("SHARED-FORMATTER-MARKER"); - }); + const result = await getIssueDetails.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId, + eventId: undefined, + issueUrl: undefined, + regionUrl: null, + }, + baseContext, + ); + + // the body is rendered from the shared formatter's content + expect(result).toContain(marker); + // ...replacing MCP's type-specific renderer + if (replacedRenderer) { + expect(result).not.toContain(replacedRenderer); + } + }, + ); it("ignores formatted.content for non-error events (transaction)", async () => { mswServer.use( @@ -369,58 +409,22 @@ describe("get_issue_details", () => { expect(result).not.toContain("TRANSACTION-SHOULD-IGNORE-THIS"); }); - it("uses formatted.content for generic events", async () => { + it("keeps the replay note when error events use formatted.content", async () => { mswServer.use( http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/", - () => HttpResponse.json(createRegressedIssue()), - { once: true }, - ), - http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/events/latest/", + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/", () => HttpResponse.json({ - ...createGenericEvent(), - formatted: { - format: "markdown", - content: "## Evidence\n\nGENERIC-FORMATTER-MARKER", + ...createDefaultEvent(), + contexts: { + replay: { + type: "default", + replay_id: "1234567890abcdef1234567890abcdef", + }, }, - }), - { once: true }, - ), - ); - - const result = await getIssueDetails.handler( - { - organizationSlug: "sentry-mcp-evals", - issueId: "MCP-SERVER-EQE", - eventId: undefined, - issueUrl: undefined, - regionUrl: null, - }, - baseContext, - ); - - expect(result).toContain("GENERIC-FORMATTER-MARKER"); - // the shared formatter replaces MCP's generic renderer - expect(result).not.toContain("### Performance Regression Details"); - }); - - it("uses formatted.content for csp events", async () => { - mswServer.use( - http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/", - () => HttpResponse.json(createCspIssue()), - { once: true }, - ), - http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/events/latest/", - () => - HttpResponse.json({ - ...createCspEvent(), formatted: { format: "markdown", - content: "## CSP\n\nCSP-FORMATTER-MARKER", + content: "## Title\n\nBODY-FROM-FORMATTER", }, }), { once: true }, @@ -430,7 +434,7 @@ describe("get_issue_details", () => { const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", - issueId: "BLOG-CSP-4XC", + issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, @@ -438,27 +442,20 @@ describe("get_issue_details", () => { baseContext, ); - expect(result).toContain("CSP-FORMATTER-MARKER"); - // the shared formatter replaces MCP's CSP renderer - expect(result).not.toContain("### CSP Violation"); + expect(result).toContain("BODY-FROM-FORMATTER"); // body from the shared formatter + expect(result).toContain("## Session Replay"); // replay note preserved (was inside formatEventOutput) }); - it("keeps the replay note when error events use formatted.content", async () => { + it("embeds the shared formatter's analysis in the Seer section when present", async () => { mswServer.use( http.get( - "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/", + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/", () => HttpResponse.json({ - ...createDefaultEvent(), - contexts: { - replay: { - type: "default", - replay_id: "1234567890abcdef1234567890abcdef", - }, - }, + autofix: { run_id: 7, status: "completed", blocks: [] }, formatted: { format: "markdown", - content: "## Title\n\nBODY-FROM-FORMATTER", + content: "## Root Cause\n\nEMBEDDED-SEER-MARKER", }, }), { once: true }, @@ -476,8 +473,10 @@ describe("get_issue_details", () => { baseContext, ); - expect(result).toContain("BODY-FROM-FORMATTER"); // body from the shared formatter - expect(result).toContain("## Session Replay"); // replay note preserved (was inside formatEventOutput) + expect(result).toContain("## Seer Analysis"); + expect(result).toContain("EMBEDDED-SEER-MARKER"); + // LLM-generated content is wrapped in the untrusted-data boundary + expect(result).toContain(''); }); it("surfaces AI conversation IDs found by bounded span lookup", async () => { From e7f534f5a62d5860ee8eb091d9fc23c58e86d71f Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Mon, 20 Jul 2026 12:23:35 -0700 Subject: [PATCH 4/4] bugbot --- packages/mcp-core/src/internal/formatting.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/mcp-core/src/internal/formatting.ts b/packages/mcp-core/src/internal/formatting.ts index 1ba718552..7bf1b952f 100644 --- a/packages/mcp-core/src/internal/formatting.ts +++ b/packages/mcp-core/src/internal/formatting.ts @@ -2169,7 +2169,10 @@ export function formatIssueOutput({ availableToolNames, directToolNames, }); - output += event.formatted.content; + const formattedContent = event.formatted.content; + output += formattedContent.endsWith("\n") + ? formattedContent + : `${formattedContent}\n`; } else { output += formatEventOutput(event, { performanceTrace,