Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/mcp-open-world-alert-webhook.md
Original file line number Diff line number Diff line change
@@ -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`.
15 changes: 15 additions & 0 deletions packages/api/src/mcp/__tests__/toolAnnotations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/mcp/tools/alerts/saveAlert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ' +
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/mcp/tools/alerts/saveWebhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ' +
Expand Down
14 changes: 12 additions & 2 deletions packages/api/src/mcp/tools/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down
Loading