Skip to content

Commit 72d7acb

Browse files
committed
feat(tools): Support errors dataset and outcome/reason filters in find_dropped_events
Mirrors two backend changes to the events-dropped endpoint: - errors dataset now maps to DataCategory.ERROR, so add it to the tool's dataset enum alongside spans/logs/metrics. - outcome (closed set of drop classifications) and reason (open sub-class) optionally scope the dropped side; accepted volume is always returned in full. getDroppedEvents forwards both filters; tool exposes them as optional params. Refs DAIN-1863
1 parent 874667f commit 72d7acb

5 files changed

Lines changed: 109 additions & 4 deletions

File tree

‎packages/mcp-core/src/api-client/client.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5222,6 +5222,8 @@ export class SentryApiService {
52225222
statsPeriod,
52235223
start,
52245224
end,
5225+
outcome,
5226+
reason,
52255227
}: {
52265228
organizationSlug: string;
52275229
interval?: string;
@@ -5230,6 +5232,8 @@ export class SentryApiService {
52305232
statsPeriod?: string;
52315233
start?: string;
52325234
end?: string;
5235+
outcome?: string;
5236+
reason?: string;
52335237
},
52345238
opts?: RequestOptions,
52355239
) {
@@ -5242,6 +5246,12 @@ export class SentryApiService {
52425246
if (projectId) {
52435247
queryParams.set("project", projectId);
52445248
}
5249+
if (outcome) {
5250+
queryParams.set("outcome", outcome);
5251+
}
5252+
if (reason) {
5253+
queryParams.set("reason", reason);
5254+
}
52455255
queryParams.set("referrer", SENTRY_MCP_SEARCH_EVENTS_REFERRER);
52465256

52475257
const apiUrl =

‎packages/mcp-core/src/skillDefinitions.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
{
2121
"name": "find_dropped_events",
22-
"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>",
22+
"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')\nfind_dropped_events(organizationSlug='my-org', dataset='errors', outcome='rate_limited')\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- Pass `outcome` and/or `reason` to scope the dropped side to one classification; the accepted volume is always returned in full.\n- An empty `droppedEvents` list means no drops in the window — the data can be trusted.\n</hints>",
2323
"requiredScopes": ["event:read"]
2424
},
2525
{

‎packages/mcp-core/src/toolDefinitions.json‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,7 +2223,7 @@
22232223
},
22242224
{
22252225
"name": "find_dropped_events",
2226-
"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>",
2226+
"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')\nfind_dropped_events(organizationSlug='my-org', dataset='errors', outcome='rate_limited')\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- Pass `outcome` and/or `reason` to scope the dropped side to one classification; the accepted volume is always returned in full.\n- An empty `droppedEvents` list means no drops in the window — the data can be trusted.\n</hints>",
22272227
"inputSchema": {
22282228
"type": "object",
22292229
"properties": {
@@ -2246,7 +2246,7 @@
22462246
"dataset": {
22472247
"default": "spans",
22482248
"type": "string",
2249-
"enum": ["spans", "logs", "metrics"],
2249+
"enum": ["spans", "logs", "metrics", "errors"],
22502250
"description": "Which data type to report drops for."
22512251
},
22522252
"projectSlug": {
@@ -2308,6 +2308,38 @@
23082308
"type": "null"
23092309
}
23102310
]
2311+
},
2312+
"outcome": {
2313+
"default": null,
2314+
"anyOf": [
2315+
{
2316+
"type": "string",
2317+
"enum": [
2318+
"rate_limited",
2319+
"filtered",
2320+
"invalid",
2321+
"abuse",
2322+
"client_discard",
2323+
"cardinality_limited"
2324+
],
2325+
"description": "Scope the dropped side to one top-level drop classification. Accepted volume is still returned in full."
2326+
},
2327+
{
2328+
"type": "null"
2329+
}
2330+
]
2331+
},
2332+
"reason": {
2333+
"default": null,
2334+
"anyOf": [
2335+
{
2336+
"type": "string",
2337+
"description": "Scope the dropped side to one reason (sub-classification within an outcome, e.g. 'spike_protection'). Combine with `outcome`."
2338+
},
2339+
{
2340+
"type": "null"
2341+
}
2342+
]
23112343
}
23122344
},
23132345
"required": ["organizationSlug"]

‎packages/mcp-core/src/tools/catalog/find-dropped-events.test.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ describe("find_dropped_events", () => {
6767
start: null,
6868
end: null,
6969
interval: null,
70+
outcome: null,
71+
reason: null,
7072
},
7173
context,
7274
);
@@ -104,4 +106,37 @@ describe("find_dropped_events", () => {
104106
}
105107
`);
106108
});
109+
110+
it("forwards the errors dataset and outcome/reason filters to the API", async () => {
111+
let captured: URLSearchParams | undefined;
112+
mswServer.use(
113+
http.get(
114+
"https://sentry.io/api/0/organizations/sentry-mcp-evals/events-dropped/",
115+
({ request }) => {
116+
captured = new URL(request.url).searchParams;
117+
return HttpResponse.json(DROPPED_EVENTS_RESPONSE);
118+
},
119+
),
120+
);
121+
122+
await findDroppedEvents.handler(
123+
{
124+
organizationSlug: "sentry-mcp-evals",
125+
regionUrl: null,
126+
dataset: "errors",
127+
projectSlug: null,
128+
statsPeriod: "24h",
129+
start: null,
130+
end: null,
131+
interval: null,
132+
outcome: "rate_limited",
133+
reason: "spike_protection",
134+
},
135+
context,
136+
);
137+
138+
expect(captured?.get("dataset")).toBe("errors");
139+
expect(captured?.get("outcome")).toBe("rate_limited");
140+
expect(captured?.get("reason")).toBe("spike_protection");
141+
});
107142
});

‎packages/mcp-core/src/tools/catalog/find-dropped-events.ts‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ import {
1010
import { setTargetTagsAndAttributes } from "../../telem/scope";
1111
import type { ServerContext } from "../../types";
1212

13-
const DROPPED_EVENTS_DATASETS = ["spans", "logs", "metrics"] as const;
13+
const DROPPED_EVENTS_DATASETS = ["spans", "logs", "metrics", "errors"] as const;
14+
15+
const DROP_OUTCOMES = [
16+
"rate_limited",
17+
"filtered",
18+
"invalid",
19+
"abuse",
20+
"client_discard",
21+
"cardinality_limited",
22+
] as const;
1423

1524
const droppedBucketSchema = z.object({
1625
outcome: z.string(),
@@ -53,11 +62,13 @@ export default defineTool({
5362
"<examples>",
5463
"find_dropped_events(organizationSlug='my-org', dataset='spans', projectSlug='my-project')",
5564
"find_dropped_events(organizationSlug='my-org', dataset='logs', statsPeriod='30d')",
65+
"find_dropped_events(organizationSlug='my-org', dataset='errors', outcome='rate_limited')",
5666
"</examples>",
5767
"",
5868
"<hints>",
5969
"- This is independent of any search query — it reports drops for the whole project/time range.",
6070
"- `outcome` is the drop kind (e.g. rate_limited, filtered); `reason` is the sub-cause (e.g. key_quota, sample_rate).",
71+
"- Pass `outcome` and/or `reason` to scope the dropped side to one classification; the accepted volume is always returned in full.",
6172
"- An empty `droppedEvents` list means no drops in the window — the data can be trusted.",
6273
"</hints>",
6374
].join("\n"),
@@ -97,6 +108,21 @@ export default defineTool({
97108
)
98109
.nullable()
99110
.default(null),
111+
outcome: z
112+
.enum(DROP_OUTCOMES)
113+
.describe(
114+
"Scope the dropped side to one top-level drop classification. Accepted volume is still returned in full.",
115+
)
116+
.nullable()
117+
.default(null),
118+
reason: z
119+
.string()
120+
.trim()
121+
.describe(
122+
"Scope the dropped side to one reason (sub-classification within an outcome, e.g. 'spike_protection'). Combine with `outcome`.",
123+
)
124+
.nullable()
125+
.default(null),
100126
},
101127
annotations: {
102128
readOnlyHint: true,
@@ -132,6 +158,8 @@ export default defineTool({
132158
statsPeriod: params.statsPeriod ?? undefined,
133159
start: params.start ?? undefined,
134160
end: params.end ?? undefined,
161+
outcome: params.outcome ?? undefined,
162+
reason: params.reason ?? undefined,
135163
});
136164

137165
const toBucket = (bucket: {

0 commit comments

Comments
 (0)