Skip to content

Commit 684cd84

Browse files
committed
feat(tools): Add onboarding status updates
Expose a directly available onboarding_status_update tool that sends explicit stage and run state to Sentry. Region-aware routing and org:read authorization reuse the existing API client path. The tool treats updates as silent UI bookkeeping, avoids echoing sensitive context, and returns whether subsequent updates should continue. Generated definitions document the shared contract.
1 parent 6102fa1 commit 684cd84

13 files changed

Lines changed: 482 additions & 6 deletions

File tree

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { USER_AGENT } from "../version";
3434
import { apiPath } from "./api-path";
3535
import { ApiNotFoundError, ApiValidationError, createApiError } from "./errors";
3636
import {
37+
AgenticOnboardingRunSchema,
3738
AIConversationDetailsResponseSchema,
3839
AIConversationSummaryListSchema,
3940
ApiErrorSchema,
@@ -92,6 +93,8 @@ import {
9293
UserSchema,
9394
} from "./schema";
9495
import type {
96+
AgenticOnboardingRun,
97+
AgenticOnboardingStatusUpdate,
9598
AIConversationDetails,
9699
AIConversationSpanList,
97100
AIConversationSummary,
@@ -1858,6 +1861,28 @@ export class SentryApiService {
18581861
* @param opts Request options
18591862
* @returns Updated project data
18601863
*/
1864+
async updateAgenticOnboardingStatus(
1865+
{
1866+
organizationSlug,
1867+
update,
1868+
}: {
1869+
organizationSlug: string;
1870+
update: AgenticOnboardingStatusUpdate;
1871+
},
1872+
opts?: RequestOptions,
1873+
): Promise<AgenticOnboardingRun> {
1874+
const body = await this.requestJSON(
1875+
`/organizations/${organizationSlug}/onboarding/agent/status/`,
1876+
{
1877+
method: "POST",
1878+
body: JSON.stringify(update),
1879+
},
1880+
opts,
1881+
);
1882+
1883+
return AgenticOnboardingRunSchema.parse(body);
1884+
}
1885+
18611886
async updateProject(
18621887
{
18631888
organizationSlug,

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,3 +2076,70 @@ export const TransactionProfileSchema = z
20762076
.optional(),
20772077
})
20782078
.passthrough();
2079+
2080+
export const AgenticOnboardingStageSchema = z.enum([
2081+
"connect_mcp",
2082+
"analyze_project",
2083+
"create_project",
2084+
"instrument_app",
2085+
"plan_test_error",
2086+
"send_verification_error",
2087+
"receive_verification_error",
2088+
"prepare_production",
2089+
"check_stack_trace_quality",
2090+
]);
2091+
2092+
export const AgenticOnboardingStageStatusSchema = z.enum([
2093+
"active",
2094+
"waiting",
2095+
"completed",
2096+
"skipped",
2097+
"failed",
2098+
]);
2099+
2100+
export const AgenticOnboardingStageStateSchema = z.object({
2101+
stage: AgenticOnboardingStageSchema,
2102+
status: AgenticOnboardingStageStatusSchema.nullable(),
2103+
eventNote: z.string().nullable(),
2104+
failureReason: z.string().nullable(),
2105+
});
2106+
2107+
export const AgenticOnboardingRunStatusSchema = z.enum([
2108+
"active",
2109+
"completed",
2110+
"failed",
2111+
"cancelled",
2112+
]);
2113+
2114+
export const AgenticOnboardingRunStatusUpdateSchema = z.enum([
2115+
"completed",
2116+
"failed",
2117+
]);
2118+
2119+
export const AgenticOnboardingStatusUpdateSchema = z.object({
2120+
schemaVersion: z.literal(1),
2121+
runToken: z.string().regex(/^[A-Za-z0-9]{10}$/),
2122+
stage: AgenticOnboardingStageSchema,
2123+
status: AgenticOnboardingStageStatusSchema,
2124+
runStatus: AgenticOnboardingRunStatusUpdateSchema.optional(),
2125+
eventNote: z.string().trim().min(1).max(256).optional(),
2126+
failureReason: z.string().trim().min(1).max(256).optional(),
2127+
projectSlug: z.string().trim().min(1).optional(),
2128+
issueId: z.string().trim().min(1).optional(),
2129+
});
2130+
2131+
export const AgenticOnboardingRunSchema = z.object({
2132+
schemaVersion: z.literal(1),
2133+
runId: z.string(),
2134+
channelId: z.string(),
2135+
clientRunId: z.string(),
2136+
createdAt: z.string(),
2137+
updatedAt: z.string(),
2138+
sequence: z.number().int().nonnegative(),
2139+
expiresAt: z.string(),
2140+
continueUpdates: z.boolean(),
2141+
runStatus: AgenticOnboardingRunStatusSchema,
2142+
projectSlug: z.string().nullable(),
2143+
issueId: z.string().nullable(),
2144+
stages: z.array(AgenticOnboardingStageStateSchema),
2145+
});

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
*/
4141
import type { z } from "zod";
4242
import type {
43+
AgenticOnboardingRunSchema,
44+
AgenticOnboardingStatusUpdateSchema,
4345
AssignedToSchema,
4446
AIConversationSummaryListSchema,
4547
AIConversationSummarySchema,
@@ -125,6 +127,10 @@ import type {
125127
UserReportListSchema,
126128
} from "./schema";
127129

130+
export type AgenticOnboardingRun = z.infer<typeof AgenticOnboardingRunSchema>;
131+
export type AgenticOnboardingStatusUpdate = z.infer<
132+
typeof AgenticOnboardingStatusUpdateSchema
133+
>;
128134
export type User = z.infer<typeof UserSchema>;
129135
export type Organization = z.infer<typeof OrganizationSchema>;
130136
export type Team = z.infer<typeof TeamSchema>;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
33
import { McpServer as ModernMcpServer } from "@modelcontextprotocol/server";
44
import { type Span, setUser, startSpan } from "@sentry/core";
55
import { mswServer } from "@sentry/mcp-server-mocks";
6-
import { http, HttpResponse } from "msw";
6+
import { HttpResponse, http } from "msw";
77
import { beforeEach, describe, expect, it, vi } from "vitest";
88
import { z } from "zod";
99
import { structuredResult } from "./internal/tool-helpers/results";
@@ -109,6 +109,7 @@ const DEFAULT_DIRECT_TOOL_NAMES = [
109109
"find_organizations",
110110
"find_projects",
111111
"get_sentry_resource",
112+
"onboarding_status_update",
112113
"search_events",
113114
"search_issues",
114115
"search_sentry_tools",
@@ -732,6 +733,7 @@ describe("buildServer", () => {
732733
"execute_sentry_tool",
733734
"find_organizations",
734735
"find_projects",
736+
"onboarding_status_update",
735737
"search_sentry_tools",
736738
],
737739
},

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "Read-only access to core Sentry data: issues, events, traces, replays, releases, cron monitors, uptime monitors, profiles, documentation, and project metadata",
66
"defaultEnabled": true,
77
"order": 1,
8-
"toolCount": 37,
8+
"toolCount": 38,
99
"tools": [
1010
{
1111
"name": "find_alert_rules",
@@ -162,6 +162,11 @@
162162
"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<examples>\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</examples>",
163163
"requiredScopes": ["project:read"]
164164
},
165+
{
166+
"name": "onboarding_status_update",
167+
"description": "Update the progress shown in Sentry's agentic onboarding UI.\n\nUse this tool only when an onboarding workflow provides a run token. Call it at the workflow boundaries described by the onboarding skill.\n\n<hints>\n- Progress updates are operational UI state, not user analytics.\n- Keep eventNote brief and limited to context useful in the progress UI.\n- Do not include source code, repository paths, credentials, error output, or other sensitive data.\n- status is required for every update.\n- Set runStatus to completed or failed only when the entire onboarding run reaches that state.\n- A failed status requires a brief failureReason.\n- failureReason is valid only with status failed.\n</hints>",
168+
"requiredScopes": ["org:read"]
169+
},
165170
{
166171
"name": "search_ai_conversations",
167172
"description": "Search Sentry AI Conversations and return one summary row per conversation.\n\nUse this tool to find or list AI Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_ai_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_ai_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_ai_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
@@ -200,7 +205,7 @@
200205
"description": "Sentry's AI debugger that helps you analyze, root cause, and fix issues",
201206
"defaultEnabled": true,
202207
"order": 2,
203-
"toolCount": 11,
208+
"toolCount": 12,
204209
"tools": [
205210
{
206211
"name": "analyze_issue_with_seer",
@@ -237,6 +242,11 @@
237242
"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, AI conversations, replays, preprod snapshots, and snapshot images.\nTrace lookups return a condensed overview by default.\n\nAI 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_ai_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=<image_file_name>: returns the image preview and metadata. Full-resolution snapshot image bytes are not available in this session.\n\nResource IDs:\n- snapshot: <snapshotId>\n\n<examples>\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</examples>",
238243
"requiredScopes": ["event:read", "project:read"]
239244
},
245+
{
246+
"name": "onboarding_status_update",
247+
"description": "Update the progress shown in Sentry's agentic onboarding UI.\n\nUse this tool only when an onboarding workflow provides a run token. Call it at the workflow boundaries described by the onboarding skill.\n\n<hints>\n- Progress updates are operational UI state, not user analytics.\n- Keep eventNote brief and limited to context useful in the progress UI.\n- Do not include source code, repository paths, credentials, error output, or other sensitive data.\n- status is required for every update.\n- Set runStatus to completed or failed only when the entire onboarding run reaches that state.\n- A failed status requires a brief failureReason.\n- failureReason is valid only with status failed.\n</hints>",
248+
"requiredScopes": ["org:read"]
249+
},
240250
{
241251
"name": "search_ai_conversations",
242252
"description": "Search Sentry AI Conversations and return one summary row per conversation.\n\nUse this tool to find or list AI Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_ai_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_ai_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_ai_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
@@ -266,7 +276,7 @@
266276
"defaultEnabled": false,
267277
"deprecated": true,
268278
"order": 3,
269-
"toolCount": 5,
279+
"toolCount": 6,
270280
"tools": [
271281
{
272282
"name": "find_organizations",
@@ -283,6 +293,11 @@
283293
"description": "Fetch the full markdown content of a Sentry documentation page.\n\nUse this tool when you need to:\n- Read the complete documentation for a specific topic\n- Get detailed implementation examples or code snippets\n- Access the full context of a documentation page\n- Extract specific sections from documentation\n\n<examples>\n### Get the Next.js integration guide\n\n```\nget_doc(path='/platforms/javascript/guides/nextjs.md')\n```\n</examples>\n\n<hints>\n- Use the path from search_docs results for accurate fetching\n- Paths should end with .md extension\n</hints>",
284294
"requiredScopes": []
285295
},
296+
{
297+
"name": "onboarding_status_update",
298+
"description": "Update the progress shown in Sentry's agentic onboarding UI.\n\nUse this tool only when an onboarding workflow provides a run token. Call it at the workflow boundaries described by the onboarding skill.\n\n<hints>\n- Progress updates are operational UI state, not user analytics.\n- Keep eventNote brief and limited to context useful in the progress UI.\n- Do not include source code, repository paths, credentials, error output, or other sensitive data.\n- status is required for every update.\n- Set runStatus to completed or failed only when the entire onboarding run reaches that state.\n- A failed status requires a brief failureReason.\n- failureReason is valid only with status failed.\n</hints>",
299+
"requiredScopes": ["org:read"]
300+
},
286301
{
287302
"name": "search_docs",
288303
"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<examples>\n```\nsearch_docs(query='Django setup configuration SENTRY_DSN', guide='python/django')\nsearch_docs(query='source maps webpack upload', guide='javascript/nextjs')\n```\n</examples>\n\n<hints>\n- Use guide parameter to filter to specific technologies (e.g., 'javascript/nextjs')\n- Include specific feature names like 'beforeSend', 'tracesSampleRate', 'SENTRY_DSN'\n</hints>",
@@ -301,7 +316,7 @@
301316
"description": "Resolve, assign, and update issues",
302317
"defaultEnabled": false,
303318
"order": 4,
304-
"toolCount": 17,
319+
"toolCount": 18,
305320
"tools": [
306321
{
307322
"name": "add_issue_note",
@@ -358,6 +373,11 @@
358373
"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, AI conversations, replays, preprod snapshots, and snapshot images.\nTrace lookups return a condensed overview by default.\n\nAI 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_ai_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=<image_file_name>: returns the image preview and metadata. Full-resolution snapshot image bytes are not available in this session.\n\nResource IDs:\n- snapshot: <snapshotId>\n\n<examples>\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</examples>",
359374
"requiredScopes": ["event:read", "project:read"]
360375
},
376+
{
377+
"name": "onboarding_status_update",
378+
"description": "Update the progress shown in Sentry's agentic onboarding UI.\n\nUse this tool only when an onboarding workflow provides a run token. Call it at the workflow boundaries described by the onboarding skill.\n\n<hints>\n- Progress updates are operational UI state, not user analytics.\n- Keep eventNote brief and limited to context useful in the progress UI.\n- Do not include source code, repository paths, credentials, error output, or other sensitive data.\n- status is required for every update.\n- Set runStatus to completed or failed only when the entire onboarding run reaches that state.\n- A failed status requires a brief failureReason.\n- failureReason is valid only with status failed.\n</hints>",
379+
"requiredScopes": ["org:read"]
380+
},
361381
{
362382
"name": "search_ai_conversations",
363383
"description": "Search Sentry AI Conversations and return one summary row per conversation.\n\nUse this tool to find or list AI Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_ai_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_ai_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_ai_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
@@ -396,7 +416,7 @@
396416
"description": "Create and modify projects, teams, DSNs, and uptime monitors",
397417
"defaultEnabled": false,
398418
"order": 5,
399-
"toolCount": 15,
419+
"toolCount": 16,
400420
"tools": [
401421
{
402422
"name": "add_team_to_project",
@@ -448,6 +468,11 @@
448468
"description": "Find teams in an organization in Sentry.\n\nUse this tool when you need to:\n- View teams in a Sentry organization\n- Find a team's slug and numeric ID to aid other tool requests\n- Search for specific teams by name or slug\n\nReturns up to 25 results. When hasMore is true, use the query parameter to narrow down results.",
449469
"requiredScopes": ["team:read"]
450470
},
471+
{
472+
"name": "onboarding_status_update",
473+
"description": "Update the progress shown in Sentry's agentic onboarding UI.\n\nUse this tool only when an onboarding workflow provides a run token. Call it at the workflow boundaries described by the onboarding skill.\n\n<hints>\n- Progress updates are operational UI state, not user analytics.\n- Keep eventNote brief and limited to context useful in the progress UI.\n- Do not include source code, repository paths, credentials, error output, or other sensitive data.\n- status is required for every update.\n- Set runStatus to completed or failed only when the entire onboarding run reaches that state.\n- A failed status requires a brief failureReason.\n- failureReason is valid only with status failed.\n</hints>",
474+
"requiredScopes": ["org:read"]
475+
},
451476
{
452477
"name": "remove_team_from_project",
453478
"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<examples>\nremove_team_from_project(organizationSlug='my-organization', projectSlug='my-project', teamSlug='my-team')\n</examples>\n\n<hints>\n- The team must already be assigned to the project.\n- This tool will not remove the last team assigned to a project.\n</hints>",

0 commit comments

Comments
 (0)