From c583250a17ba4102d97d76611607bb495d18c8e1 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Thu, 12 Nov 2026 14:21:22 -0600 Subject: [PATCH] fix(mcp): mark save_webhook and save_alert as open-world --- .changeset/mcp-open-world-alert-webhook.md | 7 +++++++ .../api/src/mcp/__tests__/toolAnnotations.test.ts | 15 +++++++++++++++ packages/api/src/mcp/tools/alerts/saveAlert.ts | 4 ++-- packages/api/src/mcp/tools/alerts/saveWebhook.ts | 4 ++-- packages/api/src/mcp/tools/annotations.ts | 14 ++++++++++++-- 5 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 .changeset/mcp-open-world-alert-webhook.md diff --git a/.changeset/mcp-open-world-alert-webhook.md b/.changeset/mcp-open-world-alert-webhook.md new file mode 100644 index 0000000000..7d7092bd77 --- /dev/null +++ b/.changeset/mcp-open-world-alert-webhook.md @@ -0,0 +1,7 @@ +--- +'@hyperdx/api': patch +--- + +fix: mark the MCP `clickstack_save_webhook` and `clickstack_save_alert` tools as open-world + +Saved webhooks and alerts send HTTP requests to third-party endpoints, so both tools now declare `openWorldHint: true`. diff --git a/packages/api/src/mcp/__tests__/toolAnnotations.test.ts b/packages/api/src/mcp/__tests__/toolAnnotations.test.ts index 4fddeae092..43c5448a48 100644 --- a/packages/api/src/mcp/__tests__/toolAnnotations.test.ts +++ b/packages/api/src/mcp/__tests__/toolAnnotations.test.ts @@ -24,6 +24,21 @@ describe('MCP tool annotations', () => { expect(missing).toEqual([]); }); + it('marks only tools that configure outbound notifications as open-world', async () => { + const client = await createTestClient(context); + const { tools } = await client.listTools(); + + const openWorld = tools + .filter(tool => tool.annotations?.openWorldHint) + .map(tool => tool.name) + .sort(); + + expect(openWorld).toEqual([ + 'clickstack_save_alert', + 'clickstack_save_webhook', + ]); + }); + it('mirrors the tool title into annotations.title', async () => { const client = await createTestClient(context); const { tools } = await client.listTools(); diff --git a/packages/api/src/mcp/tools/alerts/saveAlert.ts b/packages/api/src/mcp/tools/alerts/saveAlert.ts index 9c5962026d..b1149d1e69 100644 --- a/packages/api/src/mcp/tools/alerts/saveAlert.ts +++ b/packages/api/src/mcp/tools/alerts/saveAlert.ts @@ -8,7 +8,7 @@ import { updateAlert, validateAlertInput, } from '@/controllers/alerts'; -import { UPSERT_TOOL } from '@/mcp/tools/annotations'; +import { OPEN_WORLD_UPSERT_TOOL } from '@/mcp/tools/annotations'; import type { ToolRegistrar } from '@/mcp/tools/types'; import { formatZodIssues, @@ -43,7 +43,7 @@ export function registerSaveAlert({ 'clickstack_save_alert', { title: 'Create or Update Alert', - annotations: UPSERT_TOOL, + annotations: OPEN_WORLD_UPSERT_TOOL, description: 'Create a new alert (omit id) or update an existing one (provide id). ' + 'Alerts monitor a saved search, a dashboard tile, or an inline chart ' + diff --git a/packages/api/src/mcp/tools/alerts/saveWebhook.ts b/packages/api/src/mcp/tools/alerts/saveWebhook.ts index 5542f47f60..46c082a960 100644 --- a/packages/api/src/mcp/tools/alerts/saveWebhook.ts +++ b/packages/api/src/mcp/tools/alerts/saveWebhook.ts @@ -3,7 +3,7 @@ import mongoose from 'mongoose'; import { z } from 'zod'; import { createWebhook, updateWebhook } from '@/controllers/webhook'; -import { UPSERT_TOOL } from '@/mcp/tools/annotations'; +import { OPEN_WORLD_UPSERT_TOOL } from '@/mcp/tools/annotations'; import type { ToolRegistrar } from '@/mcp/tools/types'; import { mcpServerError, @@ -106,7 +106,7 @@ export function registerSaveWebhook({ 'clickstack_save_webhook', { title: 'Create or Update Webhook', - annotations: UPSERT_TOOL, + annotations: OPEN_WORLD_UPSERT_TOOL, description: 'Create a new webhook (omit id) or update an existing one (provide ' + 'id). Use the returned id as the webhookId when creating alerts with ' + diff --git a/packages/api/src/mcp/tools/annotations.ts b/packages/api/src/mcp/tools/annotations.ts index ed8b31b8d7..ba2821c5e7 100644 --- a/packages/api/src/mcp/tools/annotations.ts +++ b/packages/api/src/mcp/tools/annotations.ts @@ -11,8 +11,9 @@ export type ToolBehaviorAnnotations = Required< > >; -// Every tool reads or writes only the calling team's own ClickStack metadata -// (MongoDB) and telemetry (ClickHouse), so none of them is open-world. +// Tools read or write only the calling team's own ClickStack metadata +// (MongoDB) and telemetry (ClickHouse), so they are closed-world. The +// exception is OPEN_WORLD_UPSERT_TOOL. export const READ_ONLY_TOOL: ToolBehaviorAnnotations = { readOnlyHint: true, @@ -32,6 +33,15 @@ export const UPSERT_TOOL: ToolBehaviorAnnotations = { openWorldHint: false, }; +/** + * Upserts whose saved object makes ClickStack send HTTP requests to + * third-party endpoints (webhooks, and the alerts that notify them). + */ +export const OPEN_WORLD_UPSERT_TOOL: ToolBehaviorAnnotations = { + ...UPSERT_TOOL, + openWorldHint: true, +}; + /** * Updates or deletes an existing object by ID. Repeating the call with the * same arguments leaves the same end state.