Skip to content
Open
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
39 changes: 39 additions & 0 deletions packages/mcp-core/src/api-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
DashboardSchema,
DeployListSchema,
DetectorSchema,
DroppedEventsResponseSchema,
ErrorsSearchResponseSchema,
EventAttachmentListSchema,
EventSchema,
Expand Down Expand Up @@ -5207,6 +5208,44 @@ export class SentryApiService {
return EventsStatsResponseSchema.parse(body);
}

async getDroppedEvents(
{
organizationSlug,
interval,
projectId,
dataset = "spans",
statsPeriod,
start,
end,
}: {
organizationSlug: string;
interval?: string;
projectId?: string;
dataset?: EventsDataset;
statsPeriod?: string;
start?: string;
end?: string;
},
opts?: RequestOptions,
) {
const queryParams = new URLSearchParams();
queryParams.set("dataset", normalizeEventsDataset(dataset));
if (interval) {
queryParams.set("interval", interval);
}
this.applyTimeParams(queryParams, statsPeriod, start, end);
if (projectId) {
queryParams.set("project", projectId);
}
queryParams.set("referrer", SENTRY_MCP_SEARCH_EVENTS_REFERRER);

const apiUrl =
apiPath`/organizations/${organizationSlug}/events-dropped/` +
`?${queryParams.toString()}`;
const body = await this.requestJSON(apiUrl, undefined, opts);
return DroppedEventsResponseSchema.parse(body);
}

// POST https://us.sentry.io/api/0/issues/5485083130/autofix/
async startAutofix(
{
Expand Down
27 changes: 27 additions & 0 deletions packages/mcp-core/src/api-client/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2454,3 +2454,30 @@ export const EventsStatsResponseSchema = z
end: z.number().optional(),
})
.passthrough();

export const DroppedEventsBucketSchema = z
.object({
type: z.string(),
category: z.string(),
outcome: z.string(),
reason: z.string(),
start: z.number(),
end: z.number(),
count: z.number(),
})
.passthrough();

export const DroppedEventsResponseSchema = z
.object({
meta: z
.object({
dataset: z.string(),
start: z.number(),
end: z.number(),
interval: z.number(),
})
.passthrough(),
droppedEvents: z.array(DroppedEventsBucketSchema),
acceptedEvents: z.array(DroppedEventsBucketSchema),
})
.passthrough();
3 changes: 2 additions & 1 deletion packages/mcp-core/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ async function callRegisteredTool(
const DEFAULT_DIRECT_TOOL_NAMES = [
"analyze_issue_with_seer",
"execute_sentry_tool",
"find_dropped_events",
"find_organizations",
"find_projects",
"get_sentry_resource",
Expand Down Expand Up @@ -1152,7 +1153,7 @@ describe("buildServer", () => {

const result = await callRegisteredTool(server, "search_sentry_tools", {
query: "event stacktrace",
limit: 5,
limit: 8,
});
const payload = getStructuredContent<{
results: Array<{ name: string }>;
Expand Down
7 changes: 6 additions & 1 deletion packages/mcp-core/src/skillDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": 46,
"toolCount": 47,
"tools": [
{
"name": "find_alert_rules",
Expand All @@ -17,6 +17,11 @@
"description": "Find Sentry dashboards in an organization.\n\nUse this tool when you need to:\n- List dashboards in an organization\n- Find a dashboard ID before calling get_dashboard_details\n- Search dashboards by title\n\n<examples>\nfind_dashboards(organizationSlug='my-organization')\nfind_dashboards(organizationSlug='my-organization', titleQuery='errors')\n</examples>\n\n<hints>\n- Dashboard IDs are organization-scoped.\n- Use `get_dashboard_details` after finding the correct dashboard ID.\n</hints>",
"requiredScopes": ["org:read"]
},
{
"name": "find_dropped_events",
"description": "Find events dropped before they were stored in Sentry — ground-truth data-fidelity information about what was and wasn't captured.\n\nEvents can be dropped client-side in the SDK (sample_rate, before_send) or\nserver-side at ingest (rate limited, over quota, filtered, invalid, abuse/spike\nprotection, cardinality limited). Accepted-only views (searches, aggregates,\ncharts) can't show this, so the data may be incomplete in ways they don't reveal\n— for example, a flat or spiky chart caused entirely by drops.\n\nUse this tool when you need to:\n- Explain why a chart is flat, lower than expected, or doesn't match what the user is sending\n- Confirm the data you need is actually in Sentry (not dropped) before trusting a query, aggregate, or dashboard\n- Attribute a volume anomaly to a specific drop reason (quota, spike protection, sampling, filters)\n- Tell the user why their data is missing and what to do about it (raise quota, fix sampling, etc.)\n\nReturns dropped event volume bucketed over time, with the drop `outcome` and `reason`\nfor each bucket, plus the accepted volume per bucket so you can compute the dropped share.\n\n<examples>\nfind_dropped_events(organizationSlug='my-org', dataset='spans', projectSlug='my-project')\nfind_dropped_events(organizationSlug='my-org', dataset='logs', statsPeriod='30d')\n</examples>\n\n<hints>\n- This is independent of any search query — it reports drops for the whole project/time range.\n- `outcome` is the drop kind (e.g. rate_limited, filtered); `reason` is the sub-cause (e.g. key_quota, sample_rate).\n- An empty `droppedEvents` list means no drops in the window — the data can be trusted.\n</hints>",
"requiredScopes": ["event:read"]
},
{
"name": "find_metric_monitors",
"description": "Find Sentry Metric Monitors that evaluate errors, performance, logs, metrics or crash rates.\nUse this tool to find a monitor ID before inspecting its query and detection conditions with get_metric_monitor_details.\nResults contain native monitor IDs, separate from legacy metric alert IDs. Alerts connected through workflowIds control notifications; inspect them with get_alert_rule(kind='issue').\nOmit projectSlug to search all accessible projects. Pass nextCursor as cursor with the same filters to retrieve more results.\nfind_metric_monitors(organizationSlug='my-org', projectSlug='backend', query='latency')",
Expand Down
178 changes: 178 additions & 0 deletions packages/mcp-core/src/toolDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2221,6 +2221,184 @@
"skills": ["inspect"],
"surface": "catalog"
},
{
"name": "find_dropped_events",
"description": "Find events dropped before they were stored in Sentry — ground-truth data-fidelity information about what was and wasn't captured.\n\nEvents can be dropped client-side in the SDK (sample_rate, before_send) or\nserver-side at ingest (rate limited, over quota, filtered, invalid, abuse/spike\nprotection, cardinality limited). Accepted-only views (searches, aggregates,\ncharts) can't show this, so the data may be incomplete in ways they don't reveal\n— for example, a flat or spiky chart caused entirely by drops.\n\nUse this tool when you need to:\n- Explain why a chart is flat, lower than expected, or doesn't match what the user is sending\n- Confirm the data you need is actually in Sentry (not dropped) before trusting a query, aggregate, or dashboard\n- Attribute a volume anomaly to a specific drop reason (quota, spike protection, sampling, filters)\n- Tell the user why their data is missing and what to do about it (raise quota, fix sampling, etc.)\n\nReturns dropped event volume bucketed over time, with the drop `outcome` and `reason`\nfor each bucket, plus the accepted volume per bucket so you can compute the dropped share.\n\n<examples>\nfind_dropped_events(organizationSlug='my-org', dataset='spans', projectSlug='my-project')\nfind_dropped_events(organizationSlug='my-org', dataset='logs', statsPeriod='30d')\n</examples>\n\n<hints>\n- This is independent of any search query — it reports drops for the whole project/time range.\n- `outcome` is the drop kind (e.g. rate_limited, filtered); `reason` is the sub-cause (e.g. key_quota, sample_rate).\n- An empty `droppedEvents` list means no drops in the window — the data can be trusted.\n</hints>",
"inputSchema": {
"type": "object",
"properties": {
"organizationSlug": {
"type": "string",
"description": "The organization's slug. You can find a existing list of organizations you have access to using the `find_organizations()` tool."
},
"regionUrl": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "The region URL for the organization you're querying, if known. For Sentry's Cloud Service (sentry.io), this is typically the region-specific URL like 'https://us.sentry.io'. For self-hosted Sentry installations, this parameter is usually not needed and should be omitted. You can find the correct regionUrl from the organization details using the `find_organizations()` tool."
},
{
"type": "null"
}
]
},
"dataset": {
"default": "spans",
"type": "string",
"enum": ["spans", "logs", "metrics"],
"description": "Which data type to report drops for."
},
"projectSlug": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "The project's slug. You can find a list of existing projects in an organization using the `find_projects()` tool."
},
{
"type": "null"
}
]
},
"statsPeriod": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "Relative time range, e.g. '24h', '7d', '30d'. Mutually exclusive with start/end."
},
{
"type": "null"
}
]
},
"start": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "Absolute start (ISO 8601). Must be paired with end."
},
{
"type": "null"
}
]
},
"end": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "Absolute end (ISO 8601). Must be paired with start."
},
{
"type": "null"
}
]
},
"interval": {
"default": null,
"anyOf": [
{
"type": "string",
"description": "Bucket size, e.g. '1h', '1d'. Omit to let Sentry pick for the range."
},
{
"type": "null"
}
]
}
},
"required": ["organizationSlug"]
},
"outputSchema": {
"type": "object",
"properties": {
"dataset": {
"type": "string"
},
"interval": {
"type": "number"
},
"droppedEvents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"outcome": {
"type": "string"
},
"reason": {
"type": "string"
},
"category": {
"type": "string"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
},
"count": {
"type": "number"
}
},
"required": [
"outcome",
"reason",
"category",
"start",
"end",
"count"
],
"additionalProperties": false
}
},
"acceptedEvents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"outcome": {
"type": "string"
},
"reason": {
"type": "string"
},
"category": {
"type": "string"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
},
"count": {
"type": "number"
}
},
"required": [
"outcome",
"reason",
"category",
"start",
"end",
"count"
],
"additionalProperties": false
}
}
},
"required": ["dataset", "interval", "droppedEvents", "acceptedEvents"],
"additionalProperties": false
},
"requiredScopes": ["event:read"],
"skills": ["inspect"],
"surface": "direct"
},
{
"name": "find_dsns",
"description": "List all Sentry DSNs for a specific project.\n\nUse this tool when you need to:\n- Retrieve a SENTRY_DSN for a specific project\n\n<hints>\n- If the user passes a parameter in the form of name/otherName, its likely in the format of <organizationSlug>/<projectSlug>.\n- If only one parameter is provided, and it could be either `organizationSlug` or `projectSlug`, its probably `organizationSlug`, but if you're really uncertain you might want to call `find_organizations()` first.\n</hints>",
Expand Down
Loading
Loading