Skip to content

Commit 99ea130

Browse files
kathiekiwiTrigger.dev RepoOps
authored andcommitted
feat(alerts): agent tools, public API, MCP and chat delivery
Agent tools, public API and MCP support for Alerts, including chat delivery. Mono-RevId: bbbafbdc63f85c0de3b8acc36e18ac01c3d69aa7
1 parent 44667ee commit 99ea130

82 files changed

Lines changed: 8254 additions & 293 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Added MCP tools for monitoring alerts: `list_alert_capabilities`, `list_alerts`, `create_alert`, and `resolve_alert`, so an MCP client can discover what a project can watch and manage alerts directly. Health capabilities expose customizable threshold params, discoverable via `list_alert_capabilities` and settable via `create_alert`.

‎apps/webapp/app/components/dashboard-agent/tool-labels.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ const TOOL_LABELS: Record<string, string> = {
2222
get_current_page: "Reading the current page",
2323
navigate_to: "Opening the page",
2424
schedule_watch: "Filling in a watch",
25-
list_alerts: "Listing alerts",
26-
create_alert: "Creating an alert",
27-
delete_alert: "Deleting an alert",
25+
list_watch_subscriptions: "Listing alerts",
26+
create_watch_subscription: "Creating an alert",
27+
delete_watch_subscription: "Deleting an alert",
28+
describe_alert_capabilities: "Listing alert capabilities",
29+
create_monitoring_alert: "Creating an alert",
30+
update_alert_draft: "Updating the alert draft",
31+
list_monitoring_alerts: "Listing alerts",
32+
resolve_monitoring_alert: "Resolving an alert",
2833
locate: "Locating the id",
2934
submit_feedback: "Sending feedback",
3035
// Code mode.

‎apps/webapp/app/routes/api.v1.dashboard-agent.alerts.$channelId.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { z } from "zod";
33
import { resolveAgentAlertContext } from "~/services/dashboardAgentAlertContext.server";
44
import { unsubscribeChannelFromWatchAlerts } from "~/services/dashboardAgentWatchAlerts.server";
55
import { logger } from "~/services/logger.server";
6+
import { rbac } from "~/services/rbac.server";
67
import { authenticateUatOrApiRequest } from "~/services/uatRoutePreamble.server";
8+
import { canAcrossEnvironments } from "~/v3/services/dashboardResourceAccess.server";
79

810
/**
911
* `DELETE /api/v1/dashboard-agent/alerts/:channelId` — stop alerting this channel
@@ -71,13 +73,26 @@ export async function action({ request, params }: ActionFunctionArgs) {
7173
);
7274
}
7375

76+
const access = await rbac.authenticateUserActor(request, {
77+
organizationId: context.environment.organizationId,
78+
projectId: context.environment.project.id,
79+
});
80+
if (!access.ok) {
81+
return json({ error: access.error }, { status: access.status });
82+
}
83+
7484
const result = await unsubscribeChannelFromWatchAlerts(parsedParams.data.channelId, {
7585
projectId: context.environment.project.id,
7686
// A project is shared by every member, so the caller's own address is part of the scope.
7787
organizationId: context.environment.organizationId,
7888
ownerUserId: userId,
89+
authorize: (environmentTypes) =>
90+
canAcrossEnvironments(access.ability, "write", "alerts", environmentTypes),
7991
});
8092
if (!result.ok) {
93+
if (result.reason === "forbidden") {
94+
return json({ error: "You don't have permission to manage alerts" }, { status: 403 });
95+
}
8196
if (result.reason === "conflict") {
8297
return json(
8398
{ error: "That alert was being changed elsewhere. Try again.", code: "conflict" },

‎apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
watchAlertDeduplicationKey,
1717
} from "~/services/dashboardAgentWatchAlerts.server";
1818
import { logger } from "~/services/logger.server";
19+
import { rbac } from "~/services/rbac.server";
1920
import { authenticateUatOrApiRequest } from "~/services/uatRoutePreamble.server";
2021
import { canUseDashboardAgentWatches } from "~/v3/canUseDashboardAgentWatches.server";
22+
import { canAcrossEnvironments } from "~/v3/services/dashboardResourceAccess.server";
2123

2224
/**
2325
* `GET` lists this chat's project's watch alerts; `POST` subscribes the user's email. Only
@@ -168,6 +170,13 @@ export async function action({ request }: ActionFunctionArgs) {
168170
return json({ error: "Alerts are not available here", code: gate.reason }, { status: 403 });
169171
}
170172

173+
const access = await rbac.authenticateUserActor(request, {
174+
organizationId: environment.organizationId,
175+
projectId: environment.project.id,
176+
});
177+
if (!access.ok) {
178+
return json({ error: access.error }, { status: access.status });
179+
}
171180
// Only the signed-in user's own account email may be subscribed. Read off the
172181
// primary: this is the identity the subscription is pinned to.
173182
const user = await prisma.user.findFirst({ where: { id: userId }, select: { email: true } });
@@ -186,16 +195,31 @@ export async function action({ request }: ActionFunctionArgs) {
186195
);
187196
}
188197

198+
const deduplicationKey = watchAlertDeduplicationKey(email);
199+
const existing = await prisma.projectAlertChannel.findFirst({
200+
where: { projectId: environment.project.id, deduplicationKey },
201+
select: { environmentTypes: true },
202+
});
203+
const environmentTypes = [...(existing?.environmentTypes ?? []), environment.type];
204+
if (!canAcrossEnvironments(access.ability, "write", "alerts", environmentTypes)) {
205+
return json({ error: "You don't have permission to manage alerts" }, { status: 403 });
206+
}
207+
189208
try {
190209
const channel = await subscribeChannelToWatchAlerts({
191210
userId,
192211
email,
193212
// Stable per (email, project), so asking twice re-enables one channel and asking from
194213
// a second environment adds that environment to it.
195-
deduplicationKey: watchAlertDeduplicationKey(email),
214+
deduplicationKey,
196215
environmentType: environment.type,
197216
project: environment.project,
217+
authorize: (environmentTypes) =>
218+
canAcrossEnvironments(access.ability, "write", "alerts", environmentTypes),
198219
});
220+
if ("denied" in channel) {
221+
return json({ error: "You don't have permission to manage alerts" }, { status: 403 });
222+
}
199223

200224
return json({ id: channel.id, type: channel.type, target: email, enabled: channel.enabled });
201225
} catch (error) {

0 commit comments

Comments
 (0)