diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index ad3b73447..eb31e5418 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -5,7 +5,7 @@ "description": "Read-only access to core Sentry data: issues, events, traces, replays, releases, cron monitors, uptime monitors, metric monitors, profiles, documentation, and project metadata", "defaultEnabled": true, "order": 1, - "toolCount": 47, + "toolCount": 48, "tools": [ { "name": "find_alert_rules", @@ -187,6 +187,11 @@ "description": "Get details for a Sentry uptime monitor, including recent checks.\n\nUse this tool when you need to:\n- Inspect an uptime monitor's URL, interval, thresholds, and status\n- Review recent HTTP check results (success/failure, status code, duration)\n- Debug why an uptime monitor is failing\n\nThis is separate from cron monitors (`get_monitor_details`).\n\nRequest bodies are never returned. Sensitive header values are redacted.\n\n\nget_uptime_monitor_details(organizationSlug='my-organization', projectSlug='backend', uptimeMonitorId='12345')\nget_uptime_monitor_details(organizationSlug='my-organization', projectSlug='backend', uptimeMonitorId='12345', period='7d', checkLimit=20)\n", "requiredScopes": ["project:read"] }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "requiredScopes": [] + }, { "name": "search_agent_conversations", "description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n", @@ -250,7 +255,7 @@ "description": "Sentry's AI debugger that helps you analyze, root cause, and fix issues", "defaultEnabled": true, "order": 2, - "toolCount": 17, + "toolCount": 18, "tools": [ { "name": "analyze_issue_with_seer", @@ -292,6 +297,11 @@ "description": "Fetch a Sentry resource by URL, or by resourceType plus resourceId.\nPass a Sentry URL directly when possible; the resource type is auto-detected.\n\nSupports issues, events, traces, spans, agent conversations, replays, preprod snapshots, and snapshot images.\nTrace lookups return a condensed overview by default.\n\nAgent Conversations: A conversation is a set of spans sharing the same gen_ai.conversation.id. Use resourceType='ai_conversation' with a conversation ID, or pass a Sentry conversation URL, to fetch the transcript/details. To discover or list conversations, use search_agent_conversations. Conversations are NOT issues — do not use search_issues for conversation queries.\n\nFor preprod snapshot URLs (matching 'sentry.io/preprod/snapshots/'):\n- Without ?selectedSnapshot=: returns the snapshot diff summary (changed, added, removed images)\n- With ?selectedSnapshot=: returns the image preview and metadata. Full-resolution snapshot image bytes are not available in this session.\n\nResource IDs:\n- snapshot: \n\n\nget_sentry_resource(url='https://sentry.io/issues/PROJECT-123/')\nget_sentry_resource(resourceType='issue', organizationSlug='my-org', resourceId='PROJECT-123')\nget_sentry_resource(resourceType='ai_conversation', organizationSlug='my-org', resourceId='conversation-123')\nget_sentry_resource(url='https://sentry.sentry.io/preprod/snapshots/123/')\nget_sentry_resource(url='https://sentry.sentry.io/preprod/snapshots/123/?selectedSnapshot=login_screen.png')\n", "requiredScopes": ["event:read", "project:read"] }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "requiredScopes": [] + }, { "name": "search_agent_conversations", "description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n", @@ -346,7 +356,7 @@ "defaultEnabled": false, "deprecated": true, "order": 3, - "toolCount": 6, + "toolCount": 7, "tools": [ { "name": "find_organizations", @@ -368,6 +378,11 @@ "description": "Get information about the running Sentry MCP server.\n\nUse this tool when you need to:\n- Check the Sentry MCP server version\n- Verify compatibility with a Skill or integration", "requiredScopes": [] }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "requiredScopes": [] + }, { "name": "search_docs", "description": "Search Sentry documentation for SDK setup, instrumentation, and configuration guidance.\n\nUse this tool when you need to:\n- Set up Sentry SDK or framework integrations (Django, Flask, Express, Next.js, etc.)\n- Configure features like performance monitoring, error sampling, or release tracking\n- Implement custom instrumentation (spans, transactions, breadcrumbs)\n- Configure data scrubbing, filtering, or sampling rules\n\nReturns snippets only. Use the Sentry tool `get_doc` to fetch full documentation content.\n\n\n```\nsearch_docs(query='Django setup configuration SENTRY_DSN', guide='python/django')\nsearch_docs(query='source maps webpack upload', guide='javascript/nextjs')\n```\n\n\n\n- Use guide parameter to filter to specific technologies (e.g., 'javascript/nextjs')\n- Include specific feature names like 'beforeSend', 'tracesSampleRate', 'SENTRY_DSN'\n", @@ -386,7 +401,7 @@ "description": "Resolve, assign, and update issues", "defaultEnabled": false, "order": 4, - "toolCount": 25, + "toolCount": 26, "tools": [ { "name": "add_issue_note", @@ -453,6 +468,11 @@ "description": "Link an existing external ticket or GitHub pull request to a Sentry issue by URL.\nSupports native Jira, GitHub/GitHub Enterprise, GitLab, Bitbucket, and Azure DevOps integrations, and installed Sentry Apps whose issue-link forms use single-value select or text fields.\nCreates a reference: it does not create a ticket, resolve the Sentry issue, or associate a commit. Use update_issue separately to change status or assignment.\nA repeated link returns already_linked. A different App association must be unlinked first.\nFor Apps, copy the canonical issue URL from the provider. Supply fields only when the installed App requires additional form values.\n\nlink_issue(organizationSlug='my-org', issueId='PROJECT-123', externalIssueUrl='https://github.com/example/repo/pull/42')\nlink_issue(issueUrl='https://my-org.sentry.io/issues/123/', externalIssueUrl='https://linear.app/example/issue/ENG-42/fix-crash')\n", "requiredScopes": ["event:write", "org:read"] }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "requiredScopes": [] + }, { "name": "search_agent_conversations", "description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n", @@ -521,7 +541,7 @@ "description": "Create and modify projects, teams, DSNs, cron monitor environments, uptime monitors, metric monitors, and alert rules", "defaultEnabled": false, "order": 5, - "toolCount": 23, + "toolCount": 24, "tools": [ { "name": "add_team_to_project", @@ -608,6 +628,11 @@ "description": "Revoke a team's access to an existing Sentry project.\n\nUse this tool when you need to:\n- Remove a team from a project\n- Revoke team access without changing project metadata\n- Check project team assignments before removing access\n\nBe careful when using this tool because it revokes project access.\n\n\nremove_team_from_project(organizationSlug='my-organization', projectSlug='my-project', teamSlug='my-team')\n\n\n\n- The team must already be assigned to the project.\n- This tool will not remove the last team assigned to a project.\n", "requiredScopes": ["project:write", "team:read", "org:read"] }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "requiredScopes": [] + }, { "name": "update_alert_rule", "description": "Update a Sentry Alert (workflow), including notification actions and connections.\nUse get_alert_rule with kind='issue' first to inspect the complete triggers and actionFilters configuration.\nUse get_alert_options to discover notification actions, integrations, conditions, and available sources.\nOmit fields to leave them unchanged. Pass null to clear owner or environment.\ntriggers replaces the trigger conditions. actionFilters replaces ALL action groups: copy the complete configuration, retain existing IDs, and change only the intended values. Omitted groups, conditions, and actions are removed.\nFor Slack or Microsoft Teams, change config.targetDisplay to the channel name and use integrationId for the workspace or team. Sentry resolves the channel ID. Slack also accepts an explicit new targetIdentifier; a copied old ID is cleared when the name or workspace changes.\nOther actions use their provider's config and data. For Discord, PagerDuty, Opsgenie, and email, update targetIdentifier to the channel, service, team, or recipient ID; changing only its display name does not change the destination.\nUse addProjectSlugs/removeProjectSlugs to connect/disconnect a project's issue stream. Other monitors in that project remain connected. Use addDetectorIds/removeDetectorIds for individual monitors. Unmentioned connections are preserved.\nMetric Monitor detection queries and thresholds are separate operations.\nAll-project connections require Sentry's all-project feature and an API token with org:write in addition to alerts:write.\nA project-constrained session can only edit alerts affecting that project exclusively.\nRequires alerts:write; reconnect OAuth if the existing token lacks it.\n\nupdate_alert_rule(organizationSlug='my-org', ruleIdOrName='12345', status='disabled')\nupdate_alert_rule(organizationSlug='my-org', projectSlug='backend', ruleIdOrName='Notify backend team', frequencyMinutes=30)\n", diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index e686c09ba..7def0c8d1 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -6791,6 +6791,56 @@ "skills": ["project-management"], "surface": "catalog" }, + { + "name": "report_sentry_problem", + "description": "Report a problem with Sentry itself to the Sentry team as User Feedback.\n\nUse this tool when you need to:\n- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description\n- Report a Sentry API endpoint that behaves differently than documented\n- Report Sentry documentation that is wrong, outdated, or missing\n\n\nreport_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')\nreport_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')\n\n\n\n- Only report problems with Sentry products, not errors in the user's own application.\n- Tell the user what you will send before calling this tool.\n- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.\n- Keep expected and actual short and factual.\n", + "inputSchema": { + "type": "object", + "properties": { + "surface": { + "type": "string", + "enum": ["mcp", "api", "docs"], + "description": "Which Sentry surface has the problem." + }, + "target": { + "type": "string", + "minLength": 1, + "maxLength": 500, + "description": "The MCP tool name, API endpoint (method and path), or docs URL with the problem." + }, + "expected": { + "type": "string", + "minLength": 1, + "maxLength": 1000, + "description": "What you expected to happen." + }, + "actual": { + "type": "string", + "minLength": 1, + "maxLength": 2000, + "description": "What actually happened, such as the error message." + }, + "reference": { + "default": null, + "description": "Optional request ID, trace ID, or event ID returned by Sentry for the failing call.", + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + { + "type": "null" + } + ] + } + }, + "required": ["surface", "target", "expected", "actual"] + }, + "requiredScopes": [], + "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "surface": "catalog" + }, { "name": "search_agent_conversations", "description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n", diff --git a/packages/mcp-core/src/tools/catalog/index.ts b/packages/mcp-core/src/tools/catalog/index.ts index a08aec24a..14e303f63 100644 --- a/packages/mcp-core/src/tools/catalog/index.ts +++ b/packages/mcp-core/src/tools/catalog/index.ts @@ -70,6 +70,7 @@ import getAgentConversationDetails from "./get-agent-conversation-details"; import searchAgentConversations from "./search-agent-conversations"; import addIssueNote from "./add-issue-note"; import onboardingStatusUpdate from "./onboarding-status-update"; +import reportSentryProblem from "./report-sentry-problem"; import type { ToolConfig } from "../types"; const legacyGetAIConversationDetails = { @@ -178,6 +179,7 @@ const catalogTools = { search_ai_conversations: legacySearchAIConversations, add_issue_note: addIssueNote, onboarding_status_update: onboardingStatusUpdate, + report_sentry_problem: reportSentryProblem, } as const satisfies Record>; export default catalogTools; diff --git a/packages/mcp-core/src/tools/catalog/report-sentry-problem.test.ts b/packages/mcp-core/src/tools/catalog/report-sentry-problem.test.ts new file mode 100644 index 000000000..92a86a17c --- /dev/null +++ b/packages/mcp-core/src/tools/catalog/report-sentry-problem.test.ts @@ -0,0 +1,109 @@ +import { captureFeedback, getClient } from "@sentry/core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { ConfigurationError } from "../../errors.js"; +import { createTestContext } from "../../test-utils/context.js"; +import reportSentryProblem from "./report-sentry-problem.js"; + +vi.mock("@sentry/core", async (importOriginal) => ({ + ...(await importOriginal()), + captureFeedback: vi.fn(() => "feedback-event-id"), + getClient: vi.fn(), +})); + +function mockClient(dsn: object | undefined): void { + vi.mocked(getClient).mockReturnValue({ + getDsn: () => dsn, + } as unknown as ReturnType); +} + +describe("report_sentry_problem", () => { + beforeEach(() => { + vi.mocked(captureFeedback).mockClear(); + mockClient({ host: "o1.ingest.sentry.io" }); + }); + + it("sends the report as user feedback", async () => { + const result = await reportSentryProblem.handler( + { + surface: "mcp", + target: "search_issues", + expected: "Unresolved issues", + actual: "Schema validation error", + reference: "trace-123", + }, + createTestContext({ clientFamily: "claude-code" }), + ); + + expect(result).toMatchInlineSnapshot(` + "# Problem Reported + + Sent a report about **Sentry MCP** (\`search_issues\`) to the Sentry team. + + **Report ID**: feedback-event-id + + ## Response Notes + + - Tell the user the report was sent and share the report ID." + `); + expect(captureFeedback).toHaveBeenCalledWith({ + message: [ + "[Sentry MCP] search_issues", + "", + "Expected: Unresolved issues", + "Actual: Schema validation error", + "Reference: trace-123", + ].join("\n"), + source: "mcp_agent_report", + tags: { + "agent_report.surface": "mcp", + "agent_report.target": "search_issues", + "agent_report.client_family": "claude-code", + }, + }); + }); + + it("omits the reference and truncates long target tags", async () => { + const target = `https://docs.sentry.io/${"a".repeat(300)}`; + + await reportSentryProblem.handler( + { + surface: "docs", + target, + expected: "Current setup steps", + actual: "Page references a removed option", + reference: null, + }, + createTestContext(), + ); + + const params = vi.mocked(captureFeedback).mock.calls[0]?.[0]; + expect(params?.message).not.toContain("Reference:"); + expect(params?.message).toContain(target); + expect(params?.tags?.["agent_report.target"]).toHaveLength(200); + expect(params?.tags?.["agent_report.client_family"]).toBe("unknown"); + }); + + it.each([ + [ + "no Sentry client is registered", + () => vi.mocked(getClient).mockReturnValue(undefined), + ], + ["the Sentry DSN is empty", () => mockClient(undefined)], + ])("fails when %s", async (_label, setup) => { + setup(); + + await expect( + reportSentryProblem.handler( + { + surface: "api", + target: "GET /api/0/organizations/{org}/issues/", + expected: "Paginated issues", + actual: "500 error", + reference: null, + }, + createTestContext(), + ), + ).rejects.toThrow(ConfigurationError); + expect(captureFeedback).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/mcp-core/src/tools/catalog/report-sentry-problem.ts b/packages/mcp-core/src/tools/catalog/report-sentry-problem.ts new file mode 100644 index 000000000..765f8e0a2 --- /dev/null +++ b/packages/mcp-core/src/tools/catalog/report-sentry-problem.ts @@ -0,0 +1,125 @@ +import { captureFeedback, getClient } from "@sentry/core"; +import { z } from "zod"; +import { ConfigurationError } from "../../errors"; +import { defineTool } from "../../internal/tool-helpers/define"; +import { ALL_SKILLS } from "../../skills"; +import type { ServerContext } from "../../types"; + +const MAX_TAG_LENGTH = 200; + +const SURFACE_LABELS = { + mcp: "Sentry MCP", + api: "Sentry API", + docs: "Sentry docs", +} as const; + +function truncateTag(value: string): string { + return value.length > MAX_TAG_LENGTH + ? `${value.slice(0, MAX_TAG_LENGTH - 1)}…` + : value; +} + +export default defineTool({ + name: "report_sentry_problem", + skills: ALL_SKILLS, + requiredScopes: [], + description: [ + "Report a problem with Sentry itself to the Sentry team as User Feedback.", + "", + "Use this tool when you need to:", + "- Report a Sentry MCP tool that errors, returns wrong data, or behaves differently than its description", + "- Report a Sentry API endpoint that behaves differently than documented", + "- Report Sentry documentation that is wrong, outdated, or missing", + "", + "", + "report_sentry_problem(surface='mcp', target='search_issues', expected='Issues matching is:unresolved', actual='Tool returned a schema validation error')", + "report_sentry_problem(surface='docs', target='https://docs.sentry.io/platforms/python/', expected='Install command for current SDK', actual='Page references a removed option')", + "", + "", + "", + "- Only report problems with Sentry products, not errors in the user's own application.", + "- Tell the user what you will send before calling this tool.", + "- Never include credentials, tokens, prompt text, source code, customer data, or other sensitive content.", + "- Keep expected and actual short and factual.", + "", + ].join("\n"), + inputSchema: { + surface: z + .enum(["mcp", "api", "docs"]) + .describe("Which Sentry surface has the problem."), + target: z + .string() + .trim() + .min(1) + .max(500) + .describe( + "The MCP tool name, API endpoint (method and path), or docs URL with the problem.", + ), + expected: z + .string() + .trim() + .min(1) + .max(1000) + .describe("What you expected to happen."), + actual: z + .string() + .trim() + .min(1) + .max(2000) + .describe("What actually happened, such as the error message."), + reference: z + .string() + .trim() + .min(1) + .max(200) + .nullable() + .default(null) + .describe( + "Optional request ID, trace ID, or event ID returned by Sentry for the failing call.", + ), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + }, + async handler(params, context: ServerContext) { + if (!getClient()?.getDsn()) { + throw new ConfigurationError( + "Problem report not sent: Sentry telemetry is not configured for this MCP server.", + ); + } + + const message = [ + `[${SURFACE_LABELS[params.surface]}] ${params.target}`, + "", + `Expected: ${params.expected}`, + `Actual: ${params.actual}`, + params.reference ? `Reference: ${params.reference}` : null, + ] + .filter((line): line is string => line !== null) + .join("\n"); + + const feedbackId = captureFeedback({ + message, + source: "mcp_agent_report", + tags: { + "agent_report.surface": params.surface, + "agent_report.target": truncateTag(params.target), + "agent_report.client_family": context.clientFamily ?? "unknown", + }, + }); + + return [ + "# Problem Reported", + "", + `Sent a report about **${SURFACE_LABELS[params.surface]}** (\`${params.target}\`) to the Sentry team.`, + "", + `**Report ID**: ${feedbackId}`, + "", + "## Response Notes", + "", + "- Tell the user the report was sent and share the report ID.", + ].join("\n"); + }, +});