Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/contributing/tool-responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ frame (the last in-app frame of the raised exception or the thread) gets
to 200 characters of JSON. Request headers, cookies and body, frame addresses, symbols
and source links, tag search queries, and external issue link ids stay out.

### Event Stacktrace

`get_event_stacktrace` returns one thread under `thread`, using the same frame
and thread shapes as `get_issue_details`, with `selection.status` saying how the
`thread` selector resolved. When the selector matches no thread or several,
`thread` is null, `selection.message` says why, and `availableThreads` lists
every thread summary. Only the thread's most relevant frame (its last in-app
frame) gets pre/post context and variables. Held locks,
registers and the raw (unsymbolicated) stacktrace stay out.

### Profile Details

`get_profile_details` returns one profile summary under `profile`,
Expand Down
98 changes: 0 additions & 98 deletions packages/mcp-core/src/internal/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,111 +698,13 @@ function formatThreadTable(
];
}

export function formatAvailableThreadList(
threads: z.infer<typeof ThreadEntrySchema>[],
): string {
return [
"## Available Threads",
"",
...formatThreadTable(threads),
"",
"Pass `thread` as a numeric Thread ID or exact thread Name.",
].join("\n");
}

export function parseThreadsEntryData(
data: unknown,
): z.infer<typeof ThreadsEntrySchema> | undefined {
const result = ThreadsEntrySchema.safeParse(data);
return result.success ? result.data : undefined;
}

/**
* Formats the selected thread stacktrace using the same frame rendering
* conventions as issue event details.
*/
export function formatThreadStacktraceOutput({
event,
thread,
selectionReason,
}: {
event: Event;
thread: z.infer<typeof ThreadEntrySchema>;
selectionReason: string;
}): string {
const parts: string[] = [];

parts.push("## Selected Thread");
parts.push("");
parts.push(`**Selection**: ${selectionReason}`);
parts.push(`**Thread ID**: ${getThreadDisplayValue(thread.id)}`);
parts.push(`**Name**: ${getThreadDisplayValue(thread.name)}`);
parts.push(`**State**: ${getThreadDisplayValue(thread.state)}`);
parts.push(`**Crashed**: ${getThreadDisplayValue(thread.crashed)}`);
parts.push(`**Current**: ${getThreadDisplayValue(thread.current)}`);
parts.push("");

const frames = thread.stacktrace?.frames;
if (!frames || frames.length === 0) {
parts.push("No stacktrace is available for the selected thread.");
parts.push("");
return parts.join("\n");
}

parts.push("## Stacktrace");
parts.push("");
const framesOmitted = formatFramesOmitted(thread.stacktrace?.framesOmitted);
if (framesOmitted) {
parts.push(`**Frames Omitted**: ${framesOmitted}`);
parts.push("");
}

const relevantFrame = findMostRelevantInAppFrame(frames);
if (relevantFrame && (relevantFrame.context?.length || relevantFrame.vars)) {
parts.push(renderEnhancedFrame(relevantFrame, event));
parts.push("");
parts.push("**Full Stacktrace:**");
parts.push("────────────────");
} else {
parts.push("**Full Stacktrace:**");
}

parts.push(
codeBlock(
frames
.map((frame) => {
const header = formatFrameHeader(frame, undefined, event.platform);
const context = renderInlineContext(frame);
return `${header}${context}`;
})
.join("\n"),
),
);
parts.push("");

return parts.join("\n");
}

function formatFramesOmitted(
framesOmitted: unknown[] | null | undefined,
): string | null {
if (!framesOmitted?.length) {
return null;
}

const [firstOmitted, lastOmitted] = framesOmitted;
if (
typeof firstOmitted === "number" &&
typeof lastOmitted === "number" &&
Number.isFinite(firstOmitted) &&
Number.isFinite(lastOmitted)
) {
return String(Math.max(0, lastOmitted - firstOmitted));
}

return null;
}

/**
* Renders surrounding source code context for a stack frame.
* Shows a window of code lines around the error line with visual indicators.
Expand Down
2 changes: 0 additions & 2 deletions packages/mcp-core/src/server.metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ const ALLOWED_TOOL_META: Record<string, readonly string[]> = {
const OUTPUT_SCHEMA_EXEMPT: Record<string, string> = {
get_trace_details:
"markdown-only by policy (docs/contributing/tool-responses.md)",
get_event_stacktrace:
"markdown-only by policy (docs/contributing/tool-responses.md)",
get_span_details: "delegates to the get_trace_details renderer",
get_doc: "returns doc text",
get_snapshot_image: "returns an image",
Expand Down
7 changes: 4 additions & 3 deletions packages/mcp-core/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2152,9 +2152,10 @@ describe("buildServer", () => {
},
});

expect(getTextContent(result)).toContain(
"# Event Stacktrace in **sentry-mcp-evals**",
);
expect(result.structuredContent).toMatchObject({
organizationSlug: "sentry-mcp-evals",
issueId: "CLOUDFLARE-MCP-41",
});
});

it("execute_sentry_tool dispatches to catalog-only issue user reports", async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/mcp-core/src/skillDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
},
{
"name": "get_event_stacktrace",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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</hints>",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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- When the selector matches no thread or several, `thread` is null and `availableThreads` lists every thread.\n</hints>",
"requiredScopes": ["event:read"]
},
{
Expand Down Expand Up @@ -279,7 +279,7 @@
},
{
"name": "get_event_stacktrace",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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</hints>",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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- When the selector matches no thread or several, `thread` is null and `availableThreads` lists every thread.\n</hints>",
"requiredScopes": ["event:read"]
},
{
Expand Down Expand Up @@ -430,7 +430,7 @@
},
{
"name": "get_event_stacktrace",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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</hints>",
"description": "Get the full stack trace (stacktrace) for one thread of 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<examples>\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</examples>\n\n<hints>\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- When the selector matches no thread or several, `thread` is null and `availableThreads` lists every thread.\n</hints>",
"requiredScopes": ["event:read"]
},
{
Expand Down
Loading
Loading