Skip to content

Commit 8c7a1af

Browse files
shellmayrclaude
andcommitted
feat(tools): add custom inbound filter tools
Add find, create, update and delete tools for Sentry's custom inbound filters so an agent that searches for "inbound filters", "ignore errors by message" or "drop logs" finds the API instead of concluding it does not exist. The tools wrap the experimental /projects/{org}/{project}/custom-inbound-filters/ endpoints, validate condition types against the data type before the request, and turn the "feature not enabled" 400 into a message that names the feature. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 7cf4a47 commit 8c7a1af

19 files changed

Lines changed: 1953 additions & 4 deletions

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

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import {
5151
AutofixRunStateSchema,
5252
ClientKeyListSchema,
5353
ClientKeySchema,
54+
CustomInboundFilterListSchema,
55+
CustomInboundFilterSchema,
5456
CommitListSchema,
5557
CommittersResponseSchema,
5658
DashboardListSchema,
@@ -130,6 +132,8 @@ import type {
130132
SearchAgentState,
131133
ClientKey,
132134
ClientKeyList,
135+
CustomInboundFilter,
136+
CustomInboundFilterList,
133137
CommitList,
134138
CommitterList,
135139
Dashboard,
@@ -346,6 +350,13 @@ type UpdateProjectRequest = {
346350
platform?: string;
347351
};
348352

353+
type CustomInboundFilterWriteRequest = {
354+
name?: string | null;
355+
active?: boolean;
356+
dataType: string;
357+
conditions: Array<{ type: string; value: string[] }>;
358+
};
359+
349360
type UpdateClientKeyRequest = {
350361
name?: string;
351362
isActive?: boolean;
@@ -2856,6 +2867,147 @@ export class SentryApiService {
28562867
return ClientKeyListSchema.parse(body);
28572868
}
28582869

2870+
/**
2871+
* Lists the custom inbound filters of a project.
2872+
*
2873+
* Source: src/sentry/api/endpoints/project_custom_inbound_filters.py
2874+
*
2875+
* @param params Query parameters
2876+
* @param params.organizationSlug Organization identifier
2877+
* @param params.projectSlug Project identifier
2878+
* @param params.limit Maximum number of filters per page
2879+
* @param params.cursor Pagination cursor from a previous call's nextCursor
2880+
* @param opts Request options
2881+
* @returns A page of custom inbound filters, plus a cursor for the next page (null once exhausted)
2882+
*/
2883+
async listCustomInboundFilters(
2884+
{
2885+
organizationSlug,
2886+
projectSlug,
2887+
limit,
2888+
cursor,
2889+
}: {
2890+
organizationSlug: string;
2891+
projectSlug: string;
2892+
limit?: number;
2893+
cursor?: string | null;
2894+
},
2895+
opts?: RequestOptions,
2896+
): Promise<{ filters: CustomInboundFilterList; nextCursor: string | null }> {
2897+
const queryParams = new URLSearchParams();
2898+
queryParams.set("per_page", String(limit ?? 25));
2899+
if (cursor) {
2900+
queryParams.set("cursor", cursor);
2901+
}
2902+
const basePath = apiPath`/projects/${organizationSlug}/${projectSlug}/custom-inbound-filters/`;
2903+
const response = await this.request(
2904+
`${basePath}?${queryParams.toString()}`,
2905+
undefined,
2906+
opts,
2907+
);
2908+
const body = await this.parseJsonResponse(response);
2909+
return {
2910+
filters: CustomInboundFilterListSchema.parse(body),
2911+
nextCursor: getNextCursor(response.headers.get("link")),
2912+
};
2913+
}
2914+
2915+
/**
2916+
* Creates a custom inbound filter in a project.
2917+
*
2918+
* Source: src/sentry/api/endpoints/project_custom_inbound_filters.py
2919+
*/
2920+
async createCustomInboundFilter(
2921+
{
2922+
organizationSlug,
2923+
projectSlug,
2924+
...filter
2925+
}: {
2926+
organizationSlug: string;
2927+
projectSlug: string;
2928+
} & CustomInboundFilterWriteRequest,
2929+
opts?: RequestOptions,
2930+
): Promise<CustomInboundFilter> {
2931+
const body = await this.requestJSON(
2932+
apiPath`/projects/${organizationSlug}/${projectSlug}/custom-inbound-filters/`,
2933+
{
2934+
method: "POST",
2935+
body: JSON.stringify(filter),
2936+
},
2937+
opts,
2938+
);
2939+
return CustomInboundFilterSchema.parse(body);
2940+
}
2941+
2942+
/**
2943+
* Updates a custom inbound filter. Only the provided fields change.
2944+
*
2945+
* Source: src/sentry/api/endpoints/project_custom_inbound_filters.py
2946+
*/
2947+
async updateCustomInboundFilter(
2948+
{
2949+
organizationSlug,
2950+
projectSlug,
2951+
filterId,
2952+
...filter
2953+
}: {
2954+
organizationSlug: string;
2955+
projectSlug: string;
2956+
filterId: string;
2957+
} & Partial<CustomInboundFilterWriteRequest>,
2958+
opts?: RequestOptions,
2959+
): Promise<CustomInboundFilter> {
2960+
const updateData: Partial<CustomInboundFilterWriteRequest> = {};
2961+
if (filter.name !== undefined) {
2962+
updateData.name = filter.name;
2963+
}
2964+
if (filter.active !== undefined) {
2965+
updateData.active = filter.active;
2966+
}
2967+
if (filter.dataType !== undefined) {
2968+
updateData.dataType = filter.dataType;
2969+
}
2970+
if (filter.conditions !== undefined) {
2971+
updateData.conditions = filter.conditions;
2972+
}
2973+
const body = await this.requestJSON(
2974+
apiPath`/projects/${organizationSlug}/${projectSlug}/custom-inbound-filters/${filterId}/`,
2975+
{
2976+
method: "PUT",
2977+
body: JSON.stringify(updateData),
2978+
},
2979+
opts,
2980+
);
2981+
return CustomInboundFilterSchema.parse(body);
2982+
}
2983+
2984+
/**
2985+
* Deletes a custom inbound filter.
2986+
*
2987+
* Source: src/sentry/api/endpoints/project_custom_inbound_filters.py
2988+
*/
2989+
async deleteCustomInboundFilter(
2990+
{
2991+
organizationSlug,
2992+
projectSlug,
2993+
filterId,
2994+
}: {
2995+
organizationSlug: string;
2996+
projectSlug: string;
2997+
filterId: string;
2998+
},
2999+
opts?: RequestOptions,
3000+
): Promise<void> {
3001+
// Treat 404 as success so repeated deletes are idempotent.
3002+
await this.request(
3003+
apiPath`/projects/${organizationSlug}/${projectSlug}/custom-inbound-filters/${filterId}/`,
3004+
{
3005+
method: "DELETE",
3006+
},
3007+
{ ...opts, allowStatuses: [404] },
3008+
);
3009+
}
3010+
28593011
/**
28603012
* Lists releases for an organization or specific project.
28613013
*

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,25 @@ export const ClientKeySchema = z
557557

558558
export const ClientKeyListSchema = z.array(ClientKeySchema);
559559

560+
export const CustomInboundFilterConditionSchema = z.object({
561+
type: z.string(),
562+
value: z.array(z.string()),
563+
});
564+
565+
export const CustomInboundFilterSchema = z
566+
.object({
567+
id: z.union([z.string(), z.number()]),
568+
name: z.string().nullable(),
569+
active: z.boolean(),
570+
dataType: z.string(),
571+
conditions: z.array(CustomInboundFilterConditionSchema),
572+
dateCreated: z.string().datetime().nullable(),
573+
dateUpdated: z.string().datetime().nullable(),
574+
})
575+
.passthrough();
576+
577+
export const CustomInboundFilterListSchema = z.array(CustomInboundFilterSchema);
578+
560579
const ReleaseProjectSchema = z
561580
.object({
562581
id: z.union([z.string(), z.number()]),

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ import type {
5858
SearchAgentStateSchema,
5959
ClientKeyListSchema,
6060
ClientKeySchema,
61+
CustomInboundFilterListSchema,
62+
CustomInboundFilterSchema,
6163
CommitListSchema,
6264
CommitSchema,
6365
CommittersResponseSchema,
@@ -278,6 +280,10 @@ export type OrganizationEnvironmentList = z.infer<
278280
>;
279281
export type TagList = z.infer<typeof TagListSchema>;
280282
export type ClientKeyList = z.infer<typeof ClientKeyListSchema>;
283+
export type CustomInboundFilter = z.infer<typeof CustomInboundFilterSchema>;
284+
export type CustomInboundFilterList = z.infer<
285+
typeof CustomInboundFilterListSchema
286+
>;
281287

282288
// Dashboard types
283289
export type Dashboard = z.infer<typeof DashboardSchema>;

‎packages/mcp-core/src/server.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,9 @@ describe("buildServer", () => {
12651265
["remove_team_from_project", ["project-management"]],
12661266
["create_dsn", ["project-management"]],
12671267
["find_dsns", ["project-management"]],
1268+
["create_custom_inbound_filter", ["project-management"]],
1269+
["update_custom_inbound_filter", ["project-management"]],
1270+
["delete_custom_inbound_filter", ["project-management"]],
12681271
] as const) {
12691272
const grantedServer = buildServer({
12701273
context: {

0 commit comments

Comments
 (0)