Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8b53075
feat(search-events): Translate natural language queries with Seer
DominikB2014 Sep 25, 2026
9b03394
chore(api-client): Remove fake example UUID from search-agent comment
DominikB2014 Sep 25, 2026
6905405
fix(search-events): Request feature flags when checking Seer access
DominikB2014 Sep 25, 2026
0095dde
Merge origin/main into seer-search-agent-nlq
DominikB2014 Sep 25, 2026
bcf17e6
fix(search-events): Respect explicit period and grouping with Seer
DominikB2014 Sep 25, 2026
de68753
chore: Revert unrelated reformatting from the main merge
DominikB2014 Sep 25, 2026
d66e379
fix(search-events): Skip Seer for structured queries and explicit fields
DominikB2014 Sep 25, 2026
2cc0116
feat(search-events): Use Seer without a projectSlug
DominikB2014 Sep 25, 2026
1298013
feat(search-events): Search the projects Seer broadens to
DominikB2014 Sep 25, 2026
65c399c
ref(search-events): Drop Seer details from the dataset description
DominikB2014 Sep 25, 2026
74d0060
feat(search-events): Use Seer when an environment is passed
DominikB2014 Sep 25, 2026
822e6dc
feat(search-events): Return a time series when Seer sets an interval
DominikB2014 Sep 25, 2026
fd87ae7
fix(search-events): Suggest Seer's wider project scope instead of app…
DominikB2014 Sep 25, 2026
a4b026e
docs(search-events): Pass dataset in natural language examples
DominikB2014 Sep 25, 2026
24b281b
chore: Revert unrelated reformatting in search-events utils
DominikB2014 Sep 25, 2026
556b123
feat(search-events): Apply Seer's cross-event filters
DominikB2014 Sep 25, 2026
be556c1
fix(search-events): align Seer project scope with execution
skaasten Sep 28, 2026
3017556
feat(search-events): gate Seer translation behind experimental mode
skaasten Sep 28, 2026
e628801
fix(search-events): Always show omitted cross-event filter warnings
sentry-junior[bot] Sep 28, 2026
337ef2a
Merge origin/main into seer-search-agent-nlq
skaasten Sep 29, 2026
ba9c743
Merge updated PR branch after integrating main
skaasten Sep 29, 2026
eef8da8
fix(search-events): remove retired Seer access flag gate
skaasten Sep 29, 2026
f4da791
Merge origin/main into seer-search-agent-nlq
skaasten Sep 29, 2026
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
92 changes: 90 additions & 2 deletions packages/mcp-core/src/api-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import {
ReplayListResponseSchema,
ReplayRecordingSegmentsSchema,
RepositoryListSchema,
SearchAgentStartSchema,
SearchAgentStateSchema,
SpansSearchResponseSchema,
StacktraceLinkSchema,
TagListSchema,
Expand Down Expand Up @@ -117,6 +119,8 @@ import type {
AlertRuleUpdate,
AutofixRun,
AutofixRunState,
SearchAgentStart,
SearchAgentState,
ClientKey,
ClientKeyList,
CommitList,
Expand Down Expand Up @@ -175,6 +179,16 @@ import type {

const SENTRY_MCP_SEARCH_EVENTS_REFERRER = "api.mcp.search-events";

/**
* Filters on other events in the same trace, e.g. spans whose trace also has a
* matching log. Only the spans and logs datasets support them.
*/
export type CrossEventQueries = {
spanQuery?: string;
logQuery?: string;
metricQuery?: string;
};

type ExplorerAggregateParams = {
fields?: string[];
aggregateFunctions?: string[];
Expand Down Expand Up @@ -1648,12 +1662,28 @@ export class SentryApiService {
* Gets a single organization by slug.
*
* @param organizationSlug Organization identifier
* @param params Query parameters
* @param params.includeFeatureFlags Include `features` in the response (omitted by Sentry otherwise)
* @param params.detailed Include projects and teams (Sentry defaults to true)
* @param opts Request options including host override
* @returns Organization data
*/
async getOrganization(organizationSlug: string, opts?: RequestOptions) {
async getOrganization(
organizationSlug: string,
params?: { includeFeatureFlags?: boolean; detailed?: boolean },
opts?: RequestOptions,
) {
const queryParams = new URLSearchParams();
if (params?.includeFeatureFlags) {
queryParams.set("include_feature_flags", "1");
}
if (params?.detailed === false) {
queryParams.set("detailed", "0");
}
const queryString = queryParams.toString();
const organizationPath = apiPath`/organizations/${organizationSlug}/`;
const body = await this.requestJSON(
apiPath`/organizations/${organizationSlug}/`,
`${organizationPath}${queryString ? `?${queryString}` : ""}`,
undefined,
opts,
);
Expand Down Expand Up @@ -4732,6 +4762,7 @@ export class SentryApiService {
start?: string;
end?: string;
sort: string;
crossEventQueries?: CrossEventQueries;
}): URLSearchParams {
const queryParams = new URLSearchParams();

Expand All @@ -4756,6 +4787,11 @@ export class SentryApiService {
queryParams.set("sampling", "NORMAL");
}

const { spanQuery, logQuery, metricQuery } = params.crossEventQueries ?? {};
if (spanQuery) queryParams.set("spanQuery", spanQuery);
if (logQuery) queryParams.set("logQuery", logQuery);
if (metricQuery) queryParams.set("metricQuery", metricQuery);

queryParams.set("sort", params.sort);

// Add fields
Expand Down Expand Up @@ -4788,6 +4824,7 @@ export class SentryApiService {
start,
end,
sort = "-timestamp",
crossEventQueries,
}: {
organizationSlug: string;
query: string;
Expand All @@ -4799,6 +4836,7 @@ export class SentryApiService {
start?: string;
end?: string;
sort?: string;
crossEventQueries?: CrossEventQueries;
},
opts?: RequestOptions,
) {
Expand Down Expand Up @@ -4834,6 +4872,7 @@ export class SentryApiService {
start,
end,
sort,
crossEventQueries,
});
}

Expand Down Expand Up @@ -4946,6 +4985,55 @@ export class SentryApiService {
return AutofixRunStateSchema.parse(body);
}

// POST https://us.sentry.io/api/0/organizations/my-org/search-agent/start/
async startSearchAgent(
{
organizationSlug,
projectIds,
naturalLanguageQuery,
strategy,
}: {
organizationSlug: string;
projectIds: number[];
naturalLanguageQuery: string;
strategy: "Traces" | "Issues" | "Logs" | "Errors" | "Metrics";
},
opts?: RequestOptions,
): Promise<SearchAgentStart> {
const body = await this.requestJSON(
apiPath`/organizations/${organizationSlug}/search-agent/start/`,
{
method: "POST",
body: JSON.stringify({
project_ids: projectIds,
natural_language_query: naturalLanguageQuery,
strategy,
}),
},
opts,
);
return SearchAgentStartSchema.parse(body);
}

// GET https://us.sentry.io/api/0/organizations/my-org/search-agent/state/{runId}/
async getSearchAgentState(
{
organizationSlug,
runId,
}: {
organizationSlug: string;
runId: string;
},
opts?: RequestOptions,
): Promise<SearchAgentState> {
const body = await this.requestJSON(
apiPath`/organizations/${organizationSlug}/search-agent/state/${runId}/`,
undefined,
opts,
);
return SearchAgentStateSchema.parse(body);
}

/**
* Retrieves high-level metadata about a trace.
*
Expand Down
72 changes: 72 additions & 0 deletions packages/mcp-core/src/api-client/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const OrganizationSchema = z
organizationUrl: z.string().url(),
})
.optional(),
// Only returned by the organization details endpoint, not the list endpoint.
features: z.array(z.string()).optional(),
hideAiFeatures: z.boolean().optional(),
})
.passthrough();

Expand Down Expand Up @@ -1402,6 +1405,75 @@ export const AutofixRunStateSchema = z.object({
formatted: z.object({ format: z.string(), content: z.string() }).optional(),
});

/**
* Schemas for Seer's search agent, which translates natural language into
* Sentry search queries.
*
* Upstream source of truth in getsentry/sentry:
* - `src/sentry/seer/endpoints/search_agent_start.py`
* - `src/sentry/seer/endpoints/search_agent_state.py`
* - `src/sentry/seer/endpoints/search_agent_types.py`
*/
export const SearchAgentStartSchema = z
.object({
// Null until Seer has picked up the run; poll with sentry_run_id instead.
run_id: z.number().nullable(),
sentry_run_id: z.string(),
})
.passthrough();

export const SearchAgentQuerySchema = z
.object({
query: z.string(),
group_by: z.array(z.string()).default([]),
visualization: z
.array(
z
.object({
y_axes: z.array(z.string()).default([]),
// Only set when the user asks for a time bucket, e.g. "per hour".
interval: z.string().nullable().optional(),
})
.passthrough(),
)
.default([]),
sort: z.string().default(""),
// Empty when an absolute start/end range is used instead.
stats_period: z.string().default(""),
start: z.string().nullable().optional(),
end: z.string().nullable().optional(),
mode: z.string(),
// Cross-event filters, only set for the Traces strategy.
span_query: z.string().nullable().optional(),
log_query: z.string().nullable().optional(),
metric_query: z.string().nullable().optional(),
})
.passthrough();

export const SearchAgentTranslateSchema = z
.object({
responses: z.array(SearchAgentQuerySchema),
unsupported_reason: z.string().nullable().optional(),
// Projects Seer scoped the query to, a superset of the requested projects
// when it broadens scope. Absent when there's no expansion.
project_ids: z.array(z.number()).nullable().optional(),
})
.passthrough();

export const SearchAgentStateSchema = z
.object({
session: z
.object({
// Only `status` is set while the run is still being created in Seer.
status: z.string(),
final_response: SearchAgentTranslateSchema.nullable().optional(),
unsupported_reason: z.string().nullable().optional(),
})
.passthrough()
.nullable(),
})
.passthrough();

export const EventAttachmentSchema = z.object({
id: z.string(),
name: z.string(),
Expand Down
4 changes: 4 additions & 0 deletions packages/mcp-core/src/api-client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import type {
AssignedToSchema,
AutofixRunSchema,
AutofixRunStateSchema,
SearchAgentStartSchema,
SearchAgentStateSchema,
ClientKeyListSchema,
ClientKeySchema,
CommitListSchema,
Expand Down Expand Up @@ -232,6 +234,8 @@ export type EventAttachment = z.infer<typeof EventAttachmentSchema>;
export type Tag = z.infer<typeof TagSchema>;
export type AutofixRun = z.infer<typeof AutofixRunSchema>;
export type AutofixRunState = z.infer<typeof AutofixRunStateSchema>;
export type SearchAgentStart = z.infer<typeof SearchAgentStartSchema>;
export type SearchAgentState = z.infer<typeof SearchAgentStateSchema>;
export type AssignedTo = z.infer<typeof AssignedToSchema>;
export type ReplayDetails = z.infer<typeof ReplayDetailsSchema>;
export type ReplayList = z.infer<typeof ReplayListResponseSchema>["data"];
Expand Down
Loading
Loading