From 6047fbaa287e5c348ff4d5f98509f1219bfce31b Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Mon, 1 Jun 2026 16:09:20 -0600 Subject: [PATCH] refactor(mcp): rename tool prefixes from hyperdx_ to clickstack_ --- .changeset/rename-mcp-tools-to-clickstack.md | 10 + MCP.md | 46 ++--- packages/api/src/mcp/__tests__/alerts.test.ts | 64 +++--- .../api/src/mcp/__tests__/dashboards.test.ts | 186 +++++++++--------- .../api/src/mcp/__tests__/deltasTool.test.ts | 10 +- .../api/src/mcp/__tests__/queryTool.test.ts | 94 ++++----- .../src/mcp/__tests__/savedSearches.test.ts | 48 +++-- .../api/src/mcp/__tests__/sources.test.ts | 32 +-- packages/api/src/mcp/__tests__/trace.test.ts | 66 +++---- packages/api/src/mcp/mcpServer.ts | 2 +- .../api/src/mcp/prompts/dashboards/content.ts | 80 ++++---- .../api/src/mcp/prompts/dashboards/helpers.ts | 2 +- .../api/src/mcp/prompts/dashboards/index.ts | 6 +- packages/api/src/mcp/tools/alerts/getAlert.ts | 4 +- .../api/src/mcp/tools/alerts/getWebhook.ts | 6 +- .../api/src/mcp/tools/alerts/saveAlert.ts | 4 +- packages/api/src/mcp/tools/alerts/schemas.ts | 2 +- .../mcp/tools/dashboards/deleteDashboard.ts | 6 +- .../src/mcp/tools/dashboards/getDashboard.ts | 4 +- .../api/src/mcp/tools/dashboards/queryTile.ts | 8 +- .../src/mcp/tools/dashboards/saveDashboard.ts | 12 +- .../api/src/mcp/tools/dashboards/schemas.ts | 32 +-- .../api/src/mcp/tools/query/eventDeltas.ts | 18 +- .../api/src/mcp/tools/query/eventPatterns.ts | 14 +- packages/api/src/mcp/tools/query/helpers.ts | 4 +- .../src/mcp/tools/query/runEventPatterns.ts | 8 +- packages/api/src/mcp/tools/query/schemas.ts | 2 +- packages/api/src/mcp/tools/query/search.ts | 10 +- packages/api/src/mcp/tools/query/sql.ts | 16 +- packages/api/src/mcp/tools/query/table.ts | 6 +- .../api/src/mcp/tools/query/timeseries.ts | 6 +- .../mcp/tools/savedSearches/getSavedSearch.ts | 4 +- .../tools/savedSearches/saveSavedSearch.ts | 6 +- .../src/mcp/tools/savedSearches/schemas.ts | 2 +- .../src/mcp/tools/sources/describeSource.ts | 20 +- .../api/src/mcp/tools/sources/listSources.ts | 16 +- packages/api/src/mcp/tools/trace/breakdown.ts | 18 +- packages/api/src/mcp/tools/trace/waterfall.ts | 14 +- packages/api/src/mcp/utils/tracing.ts | 2 +- packages/common-utils/src/core/eventDeltas.ts | 4 +- 40 files changed, 461 insertions(+), 433 deletions(-) create mode 100644 .changeset/rename-mcp-tools-to-clickstack.md diff --git a/.changeset/rename-mcp-tools-to-clickstack.md b/.changeset/rename-mcp-tools-to-clickstack.md new file mode 100644 index 0000000000..70347fa33c --- /dev/null +++ b/.changeset/rename-mcp-tools-to-clickstack.md @@ -0,0 +1,10 @@ +--- +"@hyperdx/api": patch +"@hyperdx/common-utils": patch +--- + +refactor(mcp): rename all MCP tool prefixes from `hyperdx_` to `clickstack_` + +Rename the MCP server name from `hyperdx` to `clickstack` and update all 19 +tool names (e.g. `hyperdx_search` → `clickstack_search`), along with +descriptions, prompts, error messages, and test references. diff --git a/MCP.md b/MCP.md index 7f8e89af8c..3616e5c9a6 100644 --- a/MCP.md +++ b/MCP.md @@ -1,4 +1,4 @@ -# HyperDX MCP Server +# ClickStack MCP Server HyperDX exposes a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that @@ -41,7 +41,7 @@ instance URL (e.g. `http://localhost:8080`). ### Claude Code ```bash -claude mcp add --transport http hyperdx /api/mcp \ +claude mcp add --transport http clickstack /api/mcp \ --header "Authorization: Bearer " ``` @@ -51,7 +51,7 @@ Add the following to your [OpenCode config](https://opencode.ai/docs/config): ```json "mcp": { - "hyperdx": { + "clickstack": { "enabled": true, "type": "remote", "url": "/api/mcp", @@ -70,7 +70,7 @@ settings): ```json { "mcpServers": { - "hyperdx": { + "clickstack": { "url": "/api/mcp", "headers": { "Authorization": "Bearer " @@ -108,22 +108,22 @@ with: | Tool | Description | | ----------------------------- | -------------------------------------------------------------------------------------------- | -| `hyperdx_list_sources` | List all data sources and connections as a lightweight catalog (IDs, names, kinds) | -| `hyperdx_describe_source` | Full column schema, attribute keys, and sampled low-cardinality values for a single source | -| `hyperdx_timeseries` | Plot metrics over time as a line or stacked bar chart | -| `hyperdx_table` | Compute aggregated metrics as a table, single number, or pie chart | -| `hyperdx_search` | Browse individual log, event, or trace rows | -| `hyperdx_event_patterns` | Discover the most common log messages and event patterns using Drain clustering | -| `hyperdx_event_deltas` | Compare two row groups and rank properties by how their value distributions differ | -| `hyperdx_sql` | Execute raw ClickHouse SQL for advanced queries (JOINs, CTEs, sub-queries) | -| `hyperdx_get_dashboard` | List all dashboards or get full detail for a specific dashboard | -| `hyperdx_save_dashboard` | Create or update a dashboard with tiles (charts, tables, numbers, search, markdown) | -| `hyperdx_delete_dashboard` | Permanently delete a dashboard and its attached alerts | -| `hyperdx_query_tile` | Execute the query for a specific dashboard tile to validate results | -| `hyperdx_get_saved_search` | List all saved searches or get full detail for a specific saved search | -| `hyperdx_save_saved_search` | Create or update a saved search (reusable query against a data source) | -| `hyperdx_get_alert` | List alerts (summary) or get full detail with evaluation history; filter by state | -| `hyperdx_save_alert` | Create a new alert or update an existing one | -| `hyperdx_trace_waterfall` | Fetch all spans in a single trace as a parent/child waterfall tree with optional correlated logs | -| `hyperdx_trace_top_time_consuming_operations` | Aggregate breakdown of child operations by cumulative time across matching parent traces | -| `hyperdx_get_webhook` | List available webhook destinations for use as alert notification channels | +| `clickstack_list_sources` | List all data sources and connections as a lightweight catalog (IDs, names, kinds) | +| `clickstack_describe_source` | Full column schema, attribute keys, and sampled low-cardinality values for a single source | +| `clickstack_timeseries` | Plot metrics over time as a line or stacked bar chart | +| `clickstack_table` | Compute aggregated metrics as a table, single number, or pie chart | +| `clickstack_search` | Browse individual log, event, or trace rows | +| `clickstack_event_patterns` | Discover the most common log messages and event patterns using Drain clustering | +| `clickstack_event_deltas` | Compare two row groups and rank properties by how their value distributions differ | +| `clickstack_sql` | Execute raw ClickHouse SQL for advanced queries (JOINs, CTEs, sub-queries) | +| `clickstack_get_dashboard` | List all dashboards or get full detail for a specific dashboard | +| `clickstack_save_dashboard` | Create or update a dashboard with tiles (charts, tables, numbers, search, markdown) | +| `clickstack_delete_dashboard` | Permanently delete a dashboard and its attached alerts | +| `clickstack_query_tile` | Execute the query for a specific dashboard tile to validate results | +| `clickstack_get_saved_search` | List all saved searches or get full detail for a specific saved search | +| `clickstack_save_saved_search` | Create or update a saved search (reusable query against a data source) | +| `clickstack_get_alert` | List alerts (summary) or get full detail with evaluation history; filter by state | +| `clickstack_save_alert` | Create a new alert or update an existing one | +| `clickstack_trace_waterfall` | Fetch all spans in a single trace as a parent/child waterfall tree with optional correlated logs | +| `clickstack_trace_top_time_consuming_operations` | Aggregate breakdown of child operations by cumulative time across matching parent traces | +| `clickstack_get_webhook` | List available webhook destinations for use as alert notification channels | diff --git a/packages/api/src/mcp/__tests__/alerts.test.ts b/packages/api/src/mcp/__tests__/alerts.test.ts index 05c39a3daf..d31d851258 100644 --- a/packages/api/src/mcp/__tests__/alerts.test.ts +++ b/packages/api/src/mcp/__tests__/alerts.test.ts @@ -124,15 +124,15 @@ describe('MCP Alert Tools', () => { }).save(); } - // ─── hyperdx_get_alert ──────────────────────────────────────────────────── + // ─── clickstack_get_alert ──────────────────────────────────────────────────── - describe('hyperdx_get_alert', () => { + describe('clickstack_get_alert', () => { describe('list (no id)', () => { it('should list all alerts with slim summary fields', async () => { await createTestAlert({ name: 'Alert 1' }); await createTestAlert({ name: 'Alert 2' }); - const result = await callTool(client, 'hyperdx_get_alert', {}); + const result = await callTool(client, 'clickstack_get_alert', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -153,7 +153,7 @@ describe('MCP Alert Tools', () => { }); it('should return empty array when no alerts exist', async () => { - const result = await callTool(client, 'hyperdx_get_alert', {}); + const result = await callTool(client, 'clickstack_get_alert', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -165,7 +165,7 @@ describe('MCP Alert Tools', () => { await createTestAlert({ name: 'OK', state: AlertState.OK }); await createTestAlert({ name: 'Disabled', state: AlertState.DISABLED }); - const result = await callTool(client, 'hyperdx_get_alert', { + const result = await callTool(client, 'clickstack_get_alert', { state: 'ALERT', }); @@ -179,7 +179,7 @@ describe('MCP Alert Tools', () => { it('should return empty array when no alerts match state filter', async () => { await createTestAlert({ name: 'All Good', state: AlertState.OK }); - const result = await callTool(client, 'hyperdx_get_alert', { + const result = await callTool(client, 'clickstack_get_alert', { state: 'ALERT', }); @@ -198,7 +198,7 @@ describe('MCP Alert Tools', () => { const client2 = await createTestClient(otherTeamContext); // List should be empty for the other team - const listResult = await callTool(client2, 'hyperdx_get_alert', {}); + const listResult = await callTool(client2, 'clickstack_get_alert', {}); const output = JSON.parse(getFirstText(listResult)); expect(output).toHaveLength(0); @@ -220,7 +220,7 @@ describe('MCP Alert Tools', () => { // no name set }).save(); - const result = await callTool(client, 'hyperdx_get_alert', {}); + const result = await callTool(client, 'clickstack_get_alert', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -244,7 +244,7 @@ describe('MCP Alert Tools', () => { // no name set }).save(); - const result = await callTool(client, 'hyperdx_get_alert', {}); + const result = await callTool(client, 'clickstack_get_alert', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -257,7 +257,7 @@ describe('MCP Alert Tools', () => { it('should get full alert detail with history when valid id is provided', async () => { const alert = await createTestAlert({ name: 'Detail Test' }); - const result = await callTool(client, 'hyperdx_get_alert', { + const result = await callTool(client, 'clickstack_get_alert', { id: alert._id.toString(), }); @@ -274,7 +274,7 @@ describe('MCP Alert Tools', () => { }); it('should return error for invalid ObjectId format', async () => { - const result = await callTool(client, 'hyperdx_get_alert', { + const result = await callTool(client, 'clickstack_get_alert', { id: 'not-a-valid-id', }); @@ -284,7 +284,7 @@ describe('MCP Alert Tools', () => { it('should return error for non-existent alert id', async () => { const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_get_alert', { + const result = await callTool(client, 'clickstack_get_alert', { id: fakeId, }); @@ -301,7 +301,7 @@ describe('MCP Alert Tools', () => { }; const client2 = await createTestClient(otherTeamContext); - const getResult = await callTool(client2, 'hyperdx_get_alert', { + const getResult = await callTool(client2, 'clickstack_get_alert', { id: alert._id.toString(), }); expect(getResult.isError).toBe(true); @@ -312,15 +312,15 @@ describe('MCP Alert Tools', () => { }); }); - // ─── hyperdx_save_alert ─────────────────────────────────────────────────── + // ─── clickstack_save_alert ─────────────────────────────────────────────────── - describe('hyperdx_save_alert', () => { + describe('clickstack_save_alert', () => { describe('create', () => { it('should create a saved-search alert', async () => { const savedSearch = await createTestSavedSearch(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', savedSearchId: savedSearch._id.toString(), threshold: 50, @@ -346,7 +346,7 @@ describe('MCP Alert Tools', () => { const dashboard = await createTestDashboardWithTile(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'tile', dashboardId: dashboard._id.toString(), tileId: 'tile-1', @@ -371,7 +371,7 @@ describe('MCP Alert Tools', () => { it('should reject tile source without dashboardId', async () => { const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'tile', tileId: 'tile-1', threshold: 100, @@ -391,7 +391,7 @@ describe('MCP Alert Tools', () => { const dashboard = await createTestDashboardWithTile(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'tile', dashboardId: dashboard._id.toString(), threshold: 100, @@ -410,7 +410,7 @@ describe('MCP Alert Tools', () => { it('should reject saved_search source without savedSearchId', async () => { const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', threshold: 100, thresholdType: 'above', @@ -429,7 +429,7 @@ describe('MCP Alert Tools', () => { const webhook = await createTestWebhook(); const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', savedSearchId: fakeId, threshold: 100, @@ -448,7 +448,7 @@ describe('MCP Alert Tools', () => { it('should reject webhook channel without webhookId', async () => { const savedSearch = await createTestSavedSearch(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', savedSearchId: savedSearch._id.toString(), threshold: 100, @@ -467,7 +467,7 @@ describe('MCP Alert Tools', () => { const savedSearch = await createTestSavedSearch(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', savedSearchId: savedSearch._id.toString(), threshold: 100, @@ -487,7 +487,7 @@ describe('MCP Alert Tools', () => { const savedSearch = await createTestSavedSearch(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { source: 'saved_search', savedSearchId: savedSearch._id.toString(), threshold: 75, @@ -532,7 +532,7 @@ describe('MCP Alert Tools', () => { name: 'Original Name', }).save(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { id: alert._id.toString(), source: 'saved_search', savedSearchId: savedSearch._id.toString(), @@ -559,7 +559,7 @@ describe('MCP Alert Tools', () => { const webhook = await createTestWebhook(); const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { id: fakeId, source: 'saved_search', savedSearchId: savedSearch._id.toString(), @@ -580,7 +580,7 @@ describe('MCP Alert Tools', () => { const savedSearch = await createTestSavedSearch(); const webhook = await createTestWebhook(); - const result = await callTool(client, 'hyperdx_save_alert', { + const result = await callTool(client, 'clickstack_save_alert', { id: '!!!', source: 'saved_search', savedSearchId: savedSearch._id.toString(), @@ -599,9 +599,9 @@ describe('MCP Alert Tools', () => { }); }); - // ─── hyperdx_get_webhook ────────────────────────────────────────────────── + // ─── clickstack_get_webhook ────────────────────────────────────────────────── - describe('hyperdx_get_webhook', () => { + describe('clickstack_get_webhook', () => { it('should list all webhooks with slim fields', async () => { await Webhook.create({ team: team._id, @@ -616,7 +616,7 @@ describe('MCP Alert Tools', () => { url: 'https://example.com/hook2', }); - const result = await callTool(client, 'hyperdx_get_webhook', {}); + const result = await callTool(client, 'clickstack_get_webhook', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -635,7 +635,7 @@ describe('MCP Alert Tools', () => { }); it('should return empty array when no webhooks exist', async () => { - const result = await callTool(client, 'hyperdx_get_webhook', {}); + const result = await callTool(client, 'clickstack_get_webhook', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -656,7 +656,7 @@ describe('MCP Alert Tools', () => { }; const client2 = await createTestClient(otherTeamContext); - const listResult = await callTool(client2, 'hyperdx_get_webhook', {}); + const listResult = await callTool(client2, 'clickstack_get_webhook', {}); const output = JSON.parse(getFirstText(listResult)); expect(output).toHaveLength(0); diff --git a/packages/api/src/mcp/__tests__/dashboards.test.ts b/packages/api/src/mcp/__tests__/dashboards.test.ts index 6ed04e2ff9..5c63a326f8 100644 --- a/packages/api/src/mcp/__tests__/dashboards.test.ts +++ b/packages/api/src/mcp/__tests__/dashboards.test.ts @@ -76,7 +76,7 @@ describe('MCP Dashboard Tools', () => { await server.stop(); }); - describe('hyperdx_get_dashboard', () => { + describe('clickstack_get_dashboard', () => { it('should list all dashboards when no id provided', async () => { await new Dashboard({ name: 'Dashboard 1', @@ -91,7 +91,7 @@ describe('MCP Dashboard Tools', () => { tags: ['tag2'], }).save(); - const result = await callTool(client, 'hyperdx_get_dashboard', {}); + const result = await callTool(client, 'clickstack_get_dashboard', {}); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -109,7 +109,7 @@ describe('MCP Dashboard Tools', () => { tags: ['test'], }).save(); - const result = await callTool(client, 'hyperdx_get_dashboard', { + const result = await callTool(client, 'clickstack_get_dashboard', { id: dashboard._id.toString(), }); @@ -123,7 +123,7 @@ describe('MCP Dashboard Tools', () => { it('should return error for non-existent dashboard id', async () => { const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_get_dashboard', { + const result = await callTool(client, 'clickstack_get_dashboard', { id: fakeId, }); @@ -132,10 +132,10 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_save_dashboard', () => { + describe('clickstack_save_dashboard', () => { it('should create a new dashboard with tiles', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'New MCP Dashboard', tiles: [ { @@ -169,7 +169,7 @@ describe('MCP Dashboard Tools', () => { }); it('should create a dashboard with a markdown tile', async () => { - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Markdown Dashboard', tiles: [ { @@ -192,7 +192,7 @@ describe('MCP Dashboard Tools', () => { const sourceId = traceSource._id.toString(); // Create first - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Original Name', tiles: [ { @@ -208,7 +208,7 @@ describe('MCP Dashboard Tools', () => { const created = JSON.parse(getFirstText(createResult)); // Update - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'Updated Name', tiles: [ @@ -235,7 +235,7 @@ describe('MCP Dashboard Tools', () => { it('should return error for missing source ID', async () => { const fakeSourceId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Bad Dashboard', tiles: [ { @@ -255,7 +255,7 @@ describe('MCP Dashboard Tools', () => { it('should return error when updating non-existent dashboard', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { id: '000000000000000000000000', name: 'Ghost Dashboard', tiles: [ @@ -276,7 +276,7 @@ describe('MCP Dashboard Tools', () => { it('should create a dashboard with multiple tile types', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Multi-tile Dashboard', tiles: [ { @@ -354,7 +354,7 @@ describe('MCP Dashboard Tools', () => { it('should create a dashboard with a raw SQL tile', async () => { const connectionId = connection._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'SQL Dashboard', tiles: [ { @@ -376,7 +376,7 @@ describe('MCP Dashboard Tools', () => { it('should create a dashboard with a heatmap tile on a Trace source', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Heatmap Dashboard', tiles: [ { @@ -439,7 +439,7 @@ describe('MCP Dashboard Tools', () => { where: 'level:error AND service:checkout', }; - const saveResult = await callTool(client, 'hyperdx_save_dashboard', { + const saveResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Heatmap Full Round-Trip', tiles: [ { @@ -457,14 +457,14 @@ describe('MCP Dashboard Tools', () => { expect(saved.tiles).toHaveLength(1); expect(saved.tiles[0].config).toMatchObject(createConfig); - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: saved.id, }); expect(getResult.isError).toBeFalsy(); const fetched = JSON.parse(getFirstText(getResult)); expect(fetched.tiles[0].config).toMatchObject(createConfig); - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: saved.id, name: 'Heatmap Full Round-Trip', tiles: [ @@ -478,9 +478,13 @@ describe('MCP Dashboard Tools', () => { const updated = JSON.parse(getFirstText(updateResult)); expect(updated.tiles[0].config).toMatchObject(updatedConfig); - const getAfterUpdate = await callTool(client, 'hyperdx_get_dashboard', { - id: saved.id, - }); + const getAfterUpdate = await callTool( + client, + 'clickstack_get_dashboard', + { + id: saved.id, + }, + ); expect(getAfterUpdate.isError).toBeFalsy(); const refetched = JSON.parse(getFirstText(getAfterUpdate)); expect(refetched.tiles[0].config).toMatchObject(updatedConfig); @@ -488,7 +492,7 @@ describe('MCP Dashboard Tools', () => { it('should reject heatmap tile with empty valueExpression at the schema layer', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Bad Heatmap', tiles: [ { @@ -514,7 +518,7 @@ describe('MCP Dashboard Tools', () => { async numberFormat => { const sourceId = traceSource._id.toString(); - const saveResult = await callTool(client, 'hyperdx_save_dashboard', { + const saveResult = await callTool(client, 'clickstack_save_dashboard', { name: `NumberFormat ${numberFormat.output}`, tiles: [ { @@ -547,7 +551,7 @@ describe('MCP Dashboard Tools', () => { expect(saveResult.isError).toBeFalsy(); const saved = JSON.parse(getFirstText(saveResult)); - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: saved.id, }); expect(getResult.isError).toBeFalsy(); @@ -567,7 +571,7 @@ describe('MCP Dashboard Tools', () => { it('should reject numberFormat with an unknown output value', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Bad NumberFormat', tiles: [ { @@ -602,7 +606,7 @@ describe('MCP Dashboard Tools', () => { name: 'Logs', }); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Heatmap on Log Source', tiles: [ { @@ -634,7 +638,7 @@ describe('MCP Dashboard Tools', () => { name: 'Logs', }); - const created = await callTool(client, 'hyperdx_save_dashboard', { + const created = await callTool(client, 'clickstack_save_dashboard', { name: 'Line on Log Source', tiles: [ { @@ -650,7 +654,7 @@ describe('MCP Dashboard Tools', () => { expect(created.isError).toBeFalsy(); const saved = JSON.parse(getFirstText(created)); - const update = await callTool(client, 'hyperdx_save_dashboard', { + const update = await callTool(client, 'clickstack_save_dashboard', { id: saved.id, name: 'Line on Log Source', tiles: [ @@ -682,7 +686,7 @@ describe('MCP Dashboard Tools', () => { name: 'Logs', }); - const created = await callTool(client, 'hyperdx_save_dashboard', { + const created = await callTool(client, 'clickstack_save_dashboard', { name: 'Heatmap re-pointed at Log', tiles: [ { @@ -698,7 +702,7 @@ describe('MCP Dashboard Tools', () => { expect(created.isError).toBeFalsy(); const saved = JSON.parse(getFirstText(created)); - const update = await callTool(client, 'hyperdx_save_dashboard', { + const update = await callTool(client, 'clickstack_save_dashboard', { id: saved.id, name: 'Heatmap re-pointed at Log', tiles: [ @@ -742,7 +746,7 @@ describe('MCP Dashboard Tools', () => { numberFormat: { output: 'number' as const, average: true }, }; - const save = await callTool(client, 'hyperdx_save_dashboard', { + const save = await callTool(client, 'clickstack_save_dashboard', { name: 'Mixed Tile Round-Trip', tiles: [ { name: 'Heatmap Tile', config: heatmapConfig }, @@ -764,7 +768,7 @@ describe('MCP Dashboard Tools', () => { const fetched = JSON.parse( getFirstText( - await callTool(client, 'hyperdx_get_dashboard', { id: saved.id }), + await callTool(client, 'clickstack_get_dashboard', { id: saved.id }), ), ); const fetchedByName: Record = @@ -777,7 +781,7 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_save_dashboard - containers and tabs', () => { + describe('clickstack_save_dashboard - containers and tabs', () => { // Mirrors the v2 external-API "Containers and tabs" describe block so // the MCP path enforces the same 5 cross-field rules: container id // unique, tab id unique within a container, tile.containerId @@ -821,7 +825,7 @@ describe('MCP Dashboard Tools', () => { }, ]; - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Containers MCP Round-Trip', tiles: [ buildTile(sourceId, { @@ -884,7 +888,7 @@ describe('MCP Dashboard Tools', () => { expect(createdTilesByName.Ungrouped.tabId).toBeUndefined(); // Verify a fresh GET via the get tool also returns the structure. - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: created.id, }); const fetched = JSON.parse(getFirstText(getResult)); @@ -913,7 +917,7 @@ describe('MCP Dashboard Tools', () => { tabId: undefined, }; - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'Containers MCP Round-Trip', tiles: [ @@ -949,7 +953,7 @@ describe('MCP Dashboard Tools', () => { it('should reject duplicate container ids', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Duplicate Container Ids', tiles: [buildTile(sourceId, {})], containers: [ @@ -968,7 +972,7 @@ describe('MCP Dashboard Tools', () => { // array. Cross-tile-ref rules (covered below) fire through // collectTileContainerRefIssues and produce plain prose. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Duplicate Tab Ids', tiles: [buildTile(sourceId, {})], containers: [ @@ -992,7 +996,7 @@ describe('MCP Dashboard Tools', () => { it('should reject a tile that supplies tabId without containerId', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Tab Without Container', tiles: [buildTile(sourceId, { tabId: 'errors' })], containers: [ @@ -1013,7 +1017,7 @@ describe('MCP Dashboard Tools', () => { it('should reject a tile that references an unknown containerId', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Unknown Container', tiles: [buildTile(sourceId, { containerId: 'does-not-exist' })], containers: [{ id: 'real', title: 'Real', collapsed: false }], @@ -1027,7 +1031,7 @@ describe('MCP Dashboard Tools', () => { it('should reject a tile that references an unknown tabId within a container', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Unknown Tab', tiles: [ buildTile(sourceId, { @@ -1063,7 +1067,7 @@ describe('MCP Dashboard Tools', () => { tabs: [{ id: 'errors', title: 'Errors' }], }, ]; - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'PUT-without-containers fallback', tiles: [ buildTile(sourceId, { @@ -1077,7 +1081,7 @@ describe('MCP Dashboard Tools', () => { expect(createResult.isError).toBeFalsy(); const created = JSON.parse(getFirstText(createResult)); - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'PUT-without-containers fallback', tiles: created.tiles, @@ -1097,7 +1101,7 @@ describe('MCP Dashboard Tools', () => { // persisted containers, and the response normalizes [] back to // absent on read. const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Wipe containers', tiles: [buildTile(sourceId, { name: 'Tile' })], containers: [{ id: 'overview', title: 'Overview', collapsed: false }], @@ -1111,7 +1115,7 @@ describe('MCP Dashboard Tools', () => { containerId: undefined, tabId: undefined, }; - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'Wipe containers', tiles: [wipedTile], @@ -1121,7 +1125,7 @@ describe('MCP Dashboard Tools', () => { const updated = JSON.parse(getFirstText(updateResult)); expect(updated.containers).toBeUndefined(); - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: created.id, }); const fetched = JSON.parse(getFirstText(getResult)); @@ -1135,7 +1139,7 @@ describe('MCP Dashboard Tools', () => { it('should accept a 256-char tile.containerId and reject 257', async () => { const sourceId = traceSource._id.toString(); const idAtMax = 'c'.repeat(256); - const okResult = await callTool(client, 'hyperdx_save_dashboard', { + const okResult = await callTool(client, 'clickstack_save_dashboard', { name: '256-char containerId', tiles: [buildTile(sourceId, { containerId: idAtMax })], containers: [{ id: idAtMax, title: 'Max', collapsed: false }], @@ -1143,11 +1147,15 @@ describe('MCP Dashboard Tools', () => { expect(okResult.isError).toBeFalsy(); const idTooLong = 'c'.repeat(257); - const tooLongResult = await callTool(client, 'hyperdx_save_dashboard', { - name: '257-char containerId', - tiles: [buildTile(sourceId, { containerId: idTooLong })], - containers: [{ id: idTooLong, title: 'Too long', collapsed: false }], - }); + const tooLongResult = await callTool( + client, + 'clickstack_save_dashboard', + { + name: '257-char containerId', + tiles: [buildTile(sourceId, { containerId: idTooLong })], + containers: [{ id: idTooLong, title: 'Too long', collapsed: false }], + }, + ); expect(tooLongResult.isError).toBe(true); expect(getFirstText(tooLongResult)).toContain( 'String must contain at most 256 character(s)', @@ -1156,7 +1164,7 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_save_dashboard - dashboard filters', () => { + describe('clickstack_save_dashboard - dashboard filters', () => { // The MCP input schema delegates to createDashboardBodySchema / // updateDashboardBodySchema for the filter shape. These tests guard // that the MCP path lights up the same filter round-trip the v2 REST @@ -1179,7 +1187,7 @@ describe('MCP Dashboard Tools', () => { it('should round-trip filters on create, get, and update', async () => { const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Service Detail (MCP filter round-trip)', tiles: [traceTile(sourceId)], filters: [ @@ -1227,7 +1235,7 @@ describe('MCP Dashboard Tools', () => { }); // Fetch and assert the same shape. - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: created.id, }); const fetched = JSON.parse(getFirstText(getResult)); @@ -1235,7 +1243,7 @@ describe('MCP Dashboard Tools', () => { // Update: rename the first filter and drop the second. The first // filter keeps its id, the second is dropped (not preserved). - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'Service Detail (MCP filter round-trip)', tiles: [traceTile(sourceId)], @@ -1263,15 +1271,15 @@ describe('MCP Dashboard Tools', () => { }); it('should accept a create payload with stray filter ids (copy-paste round-trip)', async () => { - // An LLM that copies a filter out of hyperdx_get_dashboard and - // back into hyperdx_save_dashboard for a NEW dashboard ships an + // An LLM that copies a filter out of clickstack_get_dashboard and + // back into clickstack_save_dashboard for a NEW dashboard ships an // `id` on the filter. The body schema for create is // `.strict()` and rejects `id`, so saveDashboard normalizes by // stripping ids before validation. Without that normalization // this payload would 4xx with a confusing strict-validation // error. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Create with stray filter id', tiles: [traceTile(sourceId)], filters: [ @@ -1303,7 +1311,7 @@ describe('MCP Dashboard Tools', () => { // id, so saveDashboard fills one in. Without that normalization // this payload would 4xx complaining that id is missing. const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Update adds new filter', tiles: [traceTile(sourceId)], filters: [ @@ -1318,7 +1326,7 @@ describe('MCP Dashboard Tools', () => { const created = JSON.parse(getFirstText(createResult)); const existingFilterId = created.filters[0].id; - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'Update adds new filter', tiles: [traceTile(sourceId)], @@ -1360,7 +1368,7 @@ describe('MCP Dashboard Tools', () => { // saved tile would include empty-message rows instead of the // filtered set the prompt promises. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'Top Error Messages', tiles: [ { @@ -1387,14 +1395,14 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_save_dashboard - table onClick linking', () => { + describe('clickstack_save_dashboard - table onClick linking', () => { // Mirrors the v2 external-API onClick tests so the MCP path // enforces the same drill-down rules: row-click can target /search // for a log/trace source or another dashboard, by concrete ID or // by templated name, with optional whereTemplate/filters. it('should round-trip a table tile with a search onClick by ID', async () => { const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick search by id', tiles: [ { @@ -1440,7 +1448,7 @@ describe('MCP Dashboard Tools', () => { ], }); - const getResult = await callTool(client, 'hyperdx_get_dashboard', { + const getResult = await callTool(client, 'clickstack_get_dashboard', { id: created.id, }); const fetched = JSON.parse(getFirstText(getResult)); @@ -1459,7 +1467,7 @@ describe('MCP Dashboard Tools', () => { team: team._id, }).save(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick dashboard by id', tiles: [ { @@ -1511,7 +1519,7 @@ describe('MCP Dashboard Tools', () => { it('should round-trip a templated dashboard onClick (mode=template)', async () => { const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick dashboard by template', tiles: [ { @@ -1553,7 +1561,7 @@ describe('MCP Dashboard Tools', () => { it('should round-trip onClick on a raw SQL table tile', async () => { const connectionId = connection._id.toString(); const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'SQL onClick', tiles: [ { @@ -1604,7 +1612,7 @@ describe('MCP Dashboard Tools', () => { it('should reject a table tile onClick referencing a non-existent dashboard', async () => { const sourceId = traceSource._id.toString(); const ghostDashboardId = new mongoose.Types.ObjectId().toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick missing dashboard', tiles: [ { @@ -1652,7 +1660,7 @@ describe('MCP Dashboard Tools', () => { }); const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick metric source', tiles: [ { @@ -1685,7 +1693,7 @@ describe('MCP Dashboard Tools', () => { // input schema and surfaces as the SDK's "Input validation error" // envelope rather than a custom 400 message. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick bad object id', tiles: [ { @@ -1713,7 +1721,7 @@ describe('MCP Dashboard Tools', () => { // future tightening of the schema doesn't quietly change it // without updating the docs that callers rely on. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick no whereLanguage', tiles: [ { @@ -1744,7 +1752,7 @@ describe('MCP Dashboard Tools', () => { // The schema is a discriminated union on `type`; a bogus type // must be rejected up front. const sourceId = traceSource._id.toString(); - const result = await callTool(client, 'hyperdx_save_dashboard', { + const result = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick bad type', tiles: [ { @@ -1772,7 +1780,7 @@ describe('MCP Dashboard Tools', () => { // mirrors POST: missing dashboards are rejected with the same // message. const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'OnClick update validation', tiles: [ { @@ -1789,7 +1797,7 @@ describe('MCP Dashboard Tools', () => { const created = JSON.parse(getFirstText(createResult)); const ghostDashboardId = new mongoose.Types.ObjectId().toString(); - const updateResult = await callTool(client, 'hyperdx_save_dashboard', { + const updateResult = await callTool(client, 'clickstack_save_dashboard', { id: created.id, name: 'OnClick update validation', tiles: [ @@ -1814,7 +1822,7 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_delete_dashboard', () => { + describe('clickstack_delete_dashboard', () => { it('should delete an existing dashboard', async () => { const dashboard = await new Dashboard({ name: 'To Delete', @@ -1822,7 +1830,7 @@ describe('MCP Dashboard Tools', () => { team: team._id, }).save(); - const result = await callTool(client, 'hyperdx_delete_dashboard', { + const result = await callTool(client, 'clickstack_delete_dashboard', { id: dashboard._id.toString(), }); @@ -1837,7 +1845,7 @@ describe('MCP Dashboard Tools', () => { }); it('should return error for non-existent dashboard', async () => { - const result = await callTool(client, 'hyperdx_delete_dashboard', { + const result = await callTool(client, 'clickstack_delete_dashboard', { id: '000000000000000000000000', }); @@ -1846,9 +1854,9 @@ describe('MCP Dashboard Tools', () => { }); }); - describe('hyperdx_query_tile', () => { + describe('clickstack_query_tile', () => { it('should return error for non-existent dashboard', async () => { - const result = await callTool(client, 'hyperdx_query_tile', { + const result = await callTool(client, 'clickstack_query_tile', { dashboardId: '000000000000000000000000', tileId: 'some-tile-id', }); @@ -1859,7 +1867,7 @@ describe('MCP Dashboard Tools', () => { it('should return error for non-existent tile', async () => { const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Tile Query Test', tiles: [ { @@ -1874,7 +1882,7 @@ describe('MCP Dashboard Tools', () => { }); const dashboard = JSON.parse(getFirstText(createResult)); - const result = await callTool(client, 'hyperdx_query_tile', { + const result = await callTool(client, 'clickstack_query_tile', { dashboardId: dashboard.id, tileId: 'non-existent-tile-id', }); @@ -1885,7 +1893,7 @@ describe('MCP Dashboard Tools', () => { it('should return error for invalid time range', async () => { const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Time Range Test', tiles: [ { @@ -1900,7 +1908,7 @@ describe('MCP Dashboard Tools', () => { }); const dashboard = JSON.parse(getFirstText(createResult)); - const result = await callTool(client, 'hyperdx_query_tile', { + const result = await callTool(client, 'clickstack_query_tile', { dashboardId: dashboard.id, tileId: dashboard.tiles[0].id, startTime: 'not-a-date', @@ -1912,7 +1920,7 @@ describe('MCP Dashboard Tools', () => { it('should execute query for a valid tile', async () => { const sourceId = traceSource._id.toString(); - const createResult = await callTool(client, 'hyperdx_save_dashboard', { + const createResult = await callTool(client, 'clickstack_save_dashboard', { name: 'Query Tile Test', tiles: [ { @@ -1927,7 +1935,7 @@ describe('MCP Dashboard Tools', () => { }); const dashboard = JSON.parse(getFirstText(createResult)); - const result = await callTool(client, 'hyperdx_query_tile', { + const result = await callTool(client, 'clickstack_query_tile', { dashboardId: dashboard.id, tileId: dashboard.tiles[0].id, startTime: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), @@ -1945,7 +1953,7 @@ describe('MCP Dashboard Tools', () => { const prompt = buildQueryGuidePrompt(); // Ensure each section the bot called out has a heatmap entry so // the LLM cannot skip the constraints when producing a heatmap - // tile through hyperdx_save_dashboard. + // tile through clickstack_save_dashboard. const sections = [ '== AGGREGATION FUNCTIONS (aggFn) ==', '== PER-TILE TYPE CONSTRAINTS ==', @@ -2156,7 +2164,7 @@ describe('MCP Dashboard Tools', () => { const mistakesIdx = prompt.indexOf('== COMMON MISTAKES =='); const section = prompt.slice(mistakesIdx); expect(section).toMatch( - /on EVERY tile after hyperdx_save_dashboard, not just one/, + /on EVERY tile after clickstack_save_dashboard, not just one/, ); }); @@ -2295,8 +2303,8 @@ describe('MCP Dashboard Tools', () => { for (let i = 1; i <= 6; i++) { expect(wf).toMatch(new RegExp(`^${i}\\. `, 'm')); } - // Step 2 must point at hyperdx_get_dashboard with no id. - expect(wf).toMatch(/hyperdx_get_dashboard \(no id\)/); + // Step 2 must point at clickstack_get_dashboard with no id. + expect(wf).toMatch(/clickstack_get_dashboard \(no id\)/); // Step 4 must call out grouping tiles into containers before save. expect(wf).toMatch(/group them into 2-4 containers/); // Step 6 must call out EVERY tile, not just one. diff --git a/packages/api/src/mcp/__tests__/deltasTool.test.ts b/packages/api/src/mcp/__tests__/deltasTool.test.ts index 30c6868608..1d9beb5d87 100644 --- a/packages/api/src/mcp/__tests__/deltasTool.test.ts +++ b/packages/api/src/mcp/__tests__/deltasTool.test.ts @@ -71,9 +71,9 @@ describe('MCP Event Deltas Tool', () => { }); describe('schema serialization', () => { - it('exposes hyperdx_event_deltas via tools/list with target + baseline groups', async () => { + it('exposes clickstack_event_deltas via tools/list with target + baseline groups', async () => { const { tools } = await client.listTools(); - const t = tools.find(t => t.name === 'hyperdx_event_deltas'); + const t = tools.find(t => t.name === 'clickstack_event_deltas'); expect(t).toBeDefined(); const schema = t!.inputSchema; const props = Object.keys(schema.properties ?? {}); @@ -90,7 +90,7 @@ describe('MCP Event Deltas Tool', () => { describe('validation', () => { it('rejects target where endTime <= startTime', async () => { - const result = await callTool(client, 'hyperdx_event_deltas', { + const result = await callTool(client, 'clickstack_event_deltas', { sourceId: logSource._id.toString(), target: { startTime: '2026-05-10T01:00:00Z', @@ -147,7 +147,7 @@ describe('MCP Event Deltas Tool', () => { }); it('ranks SeverityText and ServiceName as top differentiating properties', async () => { - const result = await callTool(client, 'hyperdx_event_deltas', { + const result = await callTool(client, 'clickstack_event_deltas', { sourceId: logSource._id.toString(), target: { startTime: new Date(now - 5 * 60_000).toISOString(), @@ -208,7 +208,7 @@ describe('MCP Event Deltas Tool', () => { }); it('returns includeHidden:true with a hidden array containing high-cardinality / id fields', async () => { - const result = await callTool(client, 'hyperdx_event_deltas', { + const result = await callTool(client, 'clickstack_event_deltas', { sourceId: logSource._id.toString(), target: { startTime: new Date(now - 5 * 60_000).toISOString(), diff --git a/packages/api/src/mcp/__tests__/queryTool.test.ts b/packages/api/src/mcp/__tests__/queryTool.test.ts index d51a6f92a0..b5264580b5 100644 --- a/packages/api/src/mcp/__tests__/queryTool.test.ts +++ b/packages/api/src/mcp/__tests__/queryTool.test.ts @@ -87,9 +87,9 @@ describe('MCP Query Tools', () => { // ─── Schema serialization ──────────────────────────────────────────────────── describe('schema serialization', () => { - it('should expose hyperdx_timeseries with expected properties', async () => { + it('should expose clickstack_timeseries with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_timeseries'); + const tool = tools.find(t => t.name === 'clickstack_timeseries'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -102,9 +102,9 @@ describe('MCP Query Tools', () => { expect(tool!.inputSchema.required).toContain('select'); }); - it('should expose hyperdx_table with expected properties', async () => { + it('should expose clickstack_table with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_table'); + const tool = tools.find(t => t.name === 'clickstack_table'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -116,9 +116,9 @@ describe('MCP Query Tools', () => { expect(tool!.inputSchema.required).toContain('select'); }); - it('should expose hyperdx_search with expected properties', async () => { + it('should expose clickstack_search with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_search'); + const tool = tools.find(t => t.name === 'clickstack_search'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -129,9 +129,9 @@ describe('MCP Query Tools', () => { expect(tool!.inputSchema.required).toContain('sourceId'); }); - it('should expose hyperdx_event_patterns with expected properties', async () => { + it('should expose clickstack_event_patterns with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_event_patterns'); + const tool = tools.find(t => t.name === 'clickstack_event_patterns'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -143,9 +143,9 @@ describe('MCP Query Tools', () => { expect(tool!.inputSchema.required).toContain('sourceId'); }); - it('should expose hyperdx_sql with expected properties', async () => { + it('should expose clickstack_sql with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_sql'); + const tool = tools.find(t => t.name === 'clickstack_sql'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -158,11 +158,11 @@ describe('MCP Query Tools', () => { }); }); - // ─── hyperdx_timeseries ───────────────────────────────────────────────────── + // ─── clickstack_timeseries ───────────────────────────────────────────────────── - describe('hyperdx_timeseries', () => { + describe('clickstack_timeseries', () => { it('should execute a line chart query', async () => { - const result = await callTool(client, 'hyperdx_timeseries', { + const result = await callTool(client, 'clickstack_timeseries', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -176,7 +176,7 @@ describe('MCP Query Tools', () => { }); it('should execute a stacked bar chart query', async () => { - const result = await callTool(client, 'hyperdx_timeseries', { + const result = await callTool(client, 'clickstack_timeseries', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], shape: 'stacked_bar', @@ -189,7 +189,7 @@ describe('MCP Query Tools', () => { }); it('should default to line shape when shape is omitted', async () => { - const result = await callTool(client, 'hyperdx_timeseries', { + const result = await callTool(client, 'clickstack_timeseries', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], }); @@ -199,7 +199,7 @@ describe('MCP Query Tools', () => { }); it('should accept granularity in correct format', async () => { - const result = await callTool(client, 'hyperdx_timeseries', { + const result = await callTool(client, 'clickstack_timeseries', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], granularity: '1 minute', @@ -212,7 +212,7 @@ describe('MCP Query Tools', () => { }); it('should return error for invalid time range', async () => { - const result = await callTool(client, 'hyperdx_timeseries', { + const result = await callTool(client, 'clickstack_timeseries', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], startTime: 'invalid-date', @@ -223,11 +223,11 @@ describe('MCP Query Tools', () => { }); }); - // ─── hyperdx_table ────────────────────────────────────────────────────────── + // ─── clickstack_table ────────────────────────────────────────────────────────── - describe('hyperdx_table', () => { + describe('clickstack_table', () => { it('should execute a table query', async () => { - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], groupBy: 'SpanName', @@ -242,7 +242,7 @@ describe('MCP Query Tools', () => { }); it('should execute a number query', async () => { - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], shape: 'number', @@ -255,7 +255,7 @@ describe('MCP Query Tools', () => { }); it('should execute a pie query', async () => { - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], shape: 'pie', @@ -270,7 +270,7 @@ describe('MCP Query Tools', () => { it('should auto-upgrade shape:"number" to "table" when select has multiple items', async () => { // This should NOT error — it should silently upgrade to table - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [ { aggFn: 'count' }, @@ -286,7 +286,7 @@ describe('MCP Query Tools', () => { }); it('should auto-upgrade shape:"pie" to "table" when select has multiple items', async () => { - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [ { aggFn: 'count' }, @@ -303,7 +303,7 @@ describe('MCP Query Tools', () => { }); it('should use default time range when not provided', async () => { - const result = await callTool(client, 'hyperdx_table', { + const result = await callTool(client, 'clickstack_table', { sourceId: traceSource._id.toString(), select: [{ aggFn: 'count' }], }); @@ -313,11 +313,11 @@ describe('MCP Query Tools', () => { }); }); - // ─── hyperdx_search ───────────────────────────────────────────────────────── + // ─── clickstack_search ───────────────────────────────────────────────────────── - describe('hyperdx_search', () => { + describe('clickstack_search', () => { it('should execute a search query', async () => { - const result = await callTool(client, 'hyperdx_search', { + const result = await callTool(client, 'clickstack_search', { sourceId: traceSource._id.toString(), where: '', startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -329,7 +329,7 @@ describe('MCP Query Tools', () => { }); it('should respect maxResults parameter', async () => { - const result = await callTool(client, 'hyperdx_search', { + const result = await callTool(client, 'clickstack_search', { sourceId: traceSource._id.toString(), maxResults: 10, startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -340,7 +340,7 @@ describe('MCP Query Tools', () => { }); it('should use default time range when not provided', async () => { - const result = await callTool(client, 'hyperdx_search', { + const result = await callTool(client, 'clickstack_search', { sourceId: traceSource._id.toString(), }); @@ -349,7 +349,7 @@ describe('MCP Query Tools', () => { }); it('should reject calls missing sourceId', async () => { - const result = await callTool(client, 'hyperdx_search', { + const result = await callTool(client, 'clickstack_search', { where: 'level:error', }); @@ -358,11 +358,11 @@ describe('MCP Query Tools', () => { }); }); - // ─── hyperdx_event_patterns ───────────────────────────────────────────────── + // ─── clickstack_event_patterns ───────────────────────────────────────────────── - describe('hyperdx_event_patterns', () => { + describe('clickstack_event_patterns', () => { it('should execute an event_patterns query on a log source', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: logSource._id.toString(), startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), endTime: new Date().toISOString(), @@ -382,7 +382,7 @@ describe('MCP Query Tools', () => { }); it('should execute with explicit bodyExpression on trace source', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: traceSource._id.toString(), bodyExpression: 'SpanName', startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -398,7 +398,7 @@ describe('MCP Query Tools', () => { }); it('should accept custom bodyExpression on log source', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: logSource._id.toString(), bodyExpression: 'SeverityText', startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -412,7 +412,7 @@ describe('MCP Query Tools', () => { }); it('should respect sampleSize parameter', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: logSource._id.toString(), sampleSize: 100, startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -424,7 +424,7 @@ describe('MCP Query Tools', () => { }); it('should respect where filter', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: logSource._id.toString(), where: "SeverityText = 'ERROR'", whereLanguage: 'sql', @@ -437,7 +437,7 @@ describe('MCP Query Tools', () => { }); it('should reject calls missing sourceId', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), endTime: new Date().toISOString(), }); @@ -447,7 +447,7 @@ describe('MCP Query Tools', () => { }); it('should return error for non-existent source', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: '000000000000000000000000', startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), endTime: new Date().toISOString(), @@ -496,7 +496,7 @@ describe('MCP Query Tools', () => { }); it('should mine patterns from seeded data and return non-empty results', async () => { - const result = await callTool(client, 'hyperdx_event_patterns', { + const result = await callTool(client, 'clickstack_event_patterns', { sourceId: logSource._id.toString(), startTime: new Date(now.getTime() - 10 * 60 * 1000).toISOString(), endTime: new Date(now.getTime() + 60 * 1000).toISOString(), @@ -535,11 +535,11 @@ describe('MCP Query Tools', () => { }); }); - // ─── hyperdx_sql ──────────────────────────────────────────────────────────── + // ─── clickstack_sql ──────────────────────────────────────────────────────────── - describe('hyperdx_sql', () => { + describe('clickstack_sql', () => { it('should execute a raw SQL query', async () => { - const result = await callTool(client, 'hyperdx_sql', { + const result = await callTool(client, 'clickstack_sql', { connectionId: connection._id.toString(), sql: 'SELECT 1 AS value', startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -551,7 +551,7 @@ describe('MCP Query Tools', () => { }); it('should execute SQL with time macros', async () => { - const result = await callTool(client, 'hyperdx_sql', { + const result = await callTool(client, 'clickstack_sql', { connectionId: connection._id.toString(), sql: `SELECT count() AS cnt FROM ${DEFAULT_DATABASE}.${DEFAULT_TRACES_TABLE} WHERE $__timeFilter(Timestamp) LIMIT 10`, startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -563,7 +563,7 @@ describe('MCP Query Tools', () => { }); it('should use default time range when not provided', async () => { - const result = await callTool(client, 'hyperdx_sql', { + const result = await callTool(client, 'clickstack_sql', { connectionId: connection._id.toString(), sql: 'SELECT 1 AS value', }); @@ -573,7 +573,7 @@ describe('MCP Query Tools', () => { }); it('should return error for invalid time range', async () => { - const result = await callTool(client, 'hyperdx_sql', { + const result = await callTool(client, 'clickstack_sql', { connectionId: connection._id.toString(), sql: 'SELECT 1', startTime: 'not-a-date', diff --git a/packages/api/src/mcp/__tests__/savedSearches.test.ts b/packages/api/src/mcp/__tests__/savedSearches.test.ts index b63b752d95..81756fb156 100644 --- a/packages/api/src/mcp/__tests__/savedSearches.test.ts +++ b/packages/api/src/mcp/__tests__/savedSearches.test.ts @@ -87,15 +87,19 @@ describe('MCP Saved Search Tools', () => { }); } - // ─── hyperdx_get_saved_search ───────────────────────────────────────────── + // ─── clickstack_get_saved_search ───────────────────────────────────────────── - describe('hyperdx_get_saved_search', () => { + describe('clickstack_get_saved_search', () => { describe('list (no id)', () => { it('should list all saved searches with slim summary fields', async () => { await createTestSavedSearch({ name: 'Search 1' }); await createTestSavedSearch({ name: 'Search 2' }); - const result = await callTool(client, 'hyperdx_get_saved_search', {}); + const result = await callTool( + client, + 'clickstack_get_saved_search', + {}, + ); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -115,7 +119,11 @@ describe('MCP Saved Search Tools', () => { }); it('should return empty array when no saved searches exist', async () => { - const result = await callTool(client, 'hyperdx_get_saved_search', {}); + const result = await callTool( + client, + 'clickstack_get_saved_search', + {}, + ); expect(result.isError).toBeFalsy(); const output = JSON.parse(getFirstText(result)); @@ -133,7 +141,7 @@ describe('MCP Saved Search Tools', () => { const listResult = await callTool( client2, - 'hyperdx_get_saved_search', + 'clickstack_get_saved_search', {}, ); const output = JSON.parse(getFirstText(listResult)); @@ -150,7 +158,7 @@ describe('MCP Saved Search Tools', () => { where: 'level:error', }); - const result = await callTool(client, 'hyperdx_get_saved_search', { + const result = await callTool(client, 'clickstack_get_saved_search', { id: savedSearch._id.toString(), }); @@ -170,7 +178,7 @@ describe('MCP Saved Search Tools', () => { }); it('should return error for invalid ObjectId format', async () => { - const result = await callTool(client, 'hyperdx_get_saved_search', { + const result = await callTool(client, 'clickstack_get_saved_search', { id: 'not-a-valid-id', }); @@ -180,7 +188,7 @@ describe('MCP Saved Search Tools', () => { it('should return error for non-existent saved search id', async () => { const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_get_saved_search', { + const result = await callTool(client, 'clickstack_get_saved_search', { id: fakeId, }); @@ -190,12 +198,12 @@ describe('MCP Saved Search Tools', () => { }); }); - // ─── hyperdx_save_saved_search ──────────────────────────────────────────── + // ─── clickstack_save_saved_search ──────────────────────────────────────────── - describe('hyperdx_save_saved_search', () => { + describe('clickstack_save_saved_search', () => { describe('create', () => { it('should create a new saved search', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'Error Traces', sourceId: traceSource._id.toString(), where: 'StatusCode:Error', @@ -220,7 +228,7 @@ describe('MCP Saved Search Tools', () => { }); it('should create a saved search with minimal fields', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'Minimal Search', sourceId: traceSource._id.toString(), }); @@ -234,7 +242,7 @@ describe('MCP Saved Search Tools', () => { }); it('should create a saved search with SQL where language', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'SQL Search', sourceId: traceSource._id.toString(), where: "StatusCode = 'Error'", @@ -248,7 +256,7 @@ describe('MCP Saved Search Tools', () => { }); it('should create a saved search with select and orderBy', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'Full Search', sourceId: traceSource._id.toString(), select: 'body,service.name,duration', @@ -263,7 +271,7 @@ describe('MCP Saved Search Tools', () => { }); it('should create a saved search with filters', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'Filtered Search', sourceId: traceSource._id.toString(), filters: [ @@ -285,7 +293,7 @@ describe('MCP Saved Search Tools', () => { }); it('should reject invalid sourceId', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'Bad Source', sourceId: 'not-a-valid-id', }); @@ -295,7 +303,7 @@ describe('MCP Saved Search Tools', () => { }); it('should include url in response when FRONTEND_URL is set', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { name: 'URL Test', sourceId: traceSource._id.toString(), }); @@ -314,7 +322,7 @@ describe('MCP Saved Search Tools', () => { name: 'Original Name', }); - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { id: savedSearch._id.toString(), name: 'Updated Name', sourceId: traceSource._id.toString(), @@ -338,7 +346,7 @@ describe('MCP Saved Search Tools', () => { it('should return error for non-existent saved search on update', async () => { const fakeId = '000000000000000000000000'; - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { id: fakeId, name: 'Ghost Search', sourceId: traceSource._id.toString(), @@ -349,7 +357,7 @@ describe('MCP Saved Search Tools', () => { }); it('should return error for invalid ObjectId format on update', async () => { - const result = await callTool(client, 'hyperdx_save_saved_search', { + const result = await callTool(client, 'clickstack_save_saved_search', { id: '!!!', name: 'Bad ID', sourceId: traceSource._id.toString(), diff --git a/packages/api/src/mcp/__tests__/sources.test.ts b/packages/api/src/mcp/__tests__/sources.test.ts index aea5ead6d7..008e4fe852 100644 --- a/packages/api/src/mcp/__tests__/sources.test.ts +++ b/packages/api/src/mcp/__tests__/sources.test.ts @@ -96,11 +96,11 @@ describe('MCP Source Tools', () => { await server.stop(); }); - // ── hyperdx_list_sources ────────────────────────────────────────────────── + // ── clickstack_list_sources ────────────────────────────────────────────────── - describe('hyperdx_list_sources', () => { + describe('clickstack_list_sources', () => { it('should list sources and connections as a lightweight catalog', async () => { - const result = await callTool(client, 'hyperdx_list_sources'); + const result = await callTool(client, 'clickstack_list_sources'); expect(result.isError).toBeFalsy(); expect(result.content).toHaveLength(1); @@ -129,11 +129,11 @@ describe('MCP Source Tools', () => { // Should include nextStep guidance pointing to describe_source expect(output.nextStep).toBeDefined(); - expect(output.nextStep).toContain('hyperdx_describe_source'); + expect(output.nextStep).toContain('clickstack_describe_source'); }); it('should include keyColumns from source config without ClickHouse queries', async () => { - const result = await callTool(client, 'hyperdx_list_sources'); + const result = await callTool(client, 'clickstack_list_sources'); const output = JSON.parse(getFirstText(result)); const trace = output.sources.find( @@ -159,7 +159,7 @@ describe('MCP Source Tools', () => { }; const client2 = await createTestClient(context2); - const result = await callTool(client2, 'hyperdx_list_sources'); + const result = await callTool(client2, 'clickstack_list_sources'); const output = JSON.parse(getFirstText(result)); expect(output.sources).toHaveLength(0); @@ -169,11 +169,11 @@ describe('MCP Source Tools', () => { }); }); - // ── hyperdx_describe_source ─────────────────────────────────────────────── + // ── clickstack_describe_source ─────────────────────────────────────────────── - describe('hyperdx_describe_source', () => { + describe('clickstack_describe_source', () => { it('should return full column schema for a trace source', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: traceSource._id.toString(), }); @@ -197,7 +197,7 @@ describe('MCP Source Tools', () => { }); it('should return full column schema for a log source', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: logSource._id.toString(), }); @@ -223,7 +223,7 @@ describe('MCP Source Tools', () => { }); it('should include map attribute keys', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: traceSource._id.toString(), }); const output = JSON.parse(getFirstText(result)); @@ -237,7 +237,7 @@ describe('MCP Source Tools', () => { }); it('should include keyColumns for trace source', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: traceSource._id.toString(), }); const output = JSON.parse(getFirstText(result)); @@ -267,7 +267,7 @@ describe('MCP Source Tools', () => { name: 'Metrics', }); - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: metricSource._id.toString(), }); @@ -290,7 +290,7 @@ describe('MCP Source Tools', () => { }); it('should include usage guidance and nextSteps', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: traceSource._id.toString(), }); const output = JSON.parse(getFirstText(result)); @@ -301,7 +301,7 @@ describe('MCP Source Tools', () => { }); it('should return error for non-existent source', async () => { - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: '000000000000000000000000', }); @@ -330,7 +330,7 @@ describe('MCP Source Tools', () => { name: 'Other Traces', }); - const result = await callTool(client, 'hyperdx_describe_source', { + const result = await callTool(client, 'clickstack_describe_source', { sourceId: otherSource._id.toString(), }); diff --git a/packages/api/src/mcp/__tests__/trace.test.ts b/packages/api/src/mcp/__tests__/trace.test.ts index 691b55a590..adc5dd30f5 100644 --- a/packages/api/src/mcp/__tests__/trace.test.ts +++ b/packages/api/src/mcp/__tests__/trace.test.ts @@ -147,9 +147,9 @@ describe('MCP Trace Tools', () => { // ─── Schema serialization ────────────────────────────────────────────────── describe('schema serialization', () => { - it('should expose hyperdx_trace_waterfall with expected properties', async () => { + it('should expose clickstack_trace_waterfall with expected properties', async () => { const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'hyperdx_trace_waterfall'); + const tool = tools.find(t => t.name === 'clickstack_trace_waterfall'); expect(tool).toBeDefined(); const props = Object.keys(tool!.inputSchema.properties ?? {}); @@ -162,10 +162,10 @@ describe('MCP Trace Tools', () => { expect(tool!.inputSchema.required).toContain('sourceId'); }); - it('should expose hyperdx_trace_top_time_consuming_operations with expected properties', async () => { + it('should expose clickstack_trace_top_time_consuming_operations with expected properties', async () => { const { tools } = await client.listTools(); const tool = tools.find( - t => t.name === 'hyperdx_trace_top_time_consuming_operations', + t => t.name === 'clickstack_trace_top_time_consuming_operations', ); expect(tool).toBeDefined(); @@ -183,11 +183,11 @@ describe('MCP Trace Tools', () => { }); }); - // ─── hyperdx_trace_waterfall ─────────────────────────────────────────────── + // ─── clickstack_trace_waterfall ─────────────────────────────────────────────── - describe('hyperdx_trace_waterfall', () => { + describe('clickstack_trace_waterfall', () => { it('should return error for non-existent source', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: '000000000000000000000000', }); expect(result.isError).toBe(true); @@ -195,7 +195,7 @@ describe('MCP Trace Tools', () => { }); it('should return error for non-trace source', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: logSource._id.toString(), }); expect(result.isError).toBe(true); @@ -203,7 +203,7 @@ describe('MCP Trace Tools', () => { }); it('should return error for invalid time range', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), startTime: 'not-a-date', }); @@ -212,7 +212,7 @@ describe('MCP Trace Tools', () => { }); it('should return no-match hint when no traces exist', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), startTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(), endTime: new Date().toISOString(), @@ -276,7 +276,7 @@ describe('MCP Trace Tools', () => { }); it('should fetch a trace by traceId and return a waterfall tree', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), traceId: TRACE_ID, startTime: new Date(now.getTime() - 10 * 60 * 1000).toISOString(), @@ -306,7 +306,7 @@ describe('MCP Trace Tools', () => { }); it('should auto-pick the slowest trace when traceId is omitted', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), pickFilter: `ServiceName:${WF_SVC}`, pickBy: 'slowest', @@ -321,7 +321,7 @@ describe('MCP Trace Tools', () => { }); it('should auto-pick the most recent trace', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), pickFilter: `ServiceName:${WF_SVC}`, pickBy: 'most_recent', @@ -335,7 +335,7 @@ describe('MCP Trace Tools', () => { }); it('should respect maxSpans and note truncation', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), traceId: TRACE_ID, maxSpans: 2, @@ -371,7 +371,7 @@ describe('MCP Trace Tools', () => { }, ]); - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), traceId: TRACE_ID, includeLogs: true, @@ -388,7 +388,7 @@ describe('MCP Trace Tools', () => { }); it('should omit logs when includeLogs is false', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), traceId: TRACE_ID, includeLogs: false, @@ -402,7 +402,7 @@ describe('MCP Trace Tools', () => { }); it('should apply pickFilter to narrow which trace is picked', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), pickFilter: `ServiceName:${WF_SVC}`, pickBy: 'slowest', @@ -416,7 +416,7 @@ describe('MCP Trace Tools', () => { }); it('should apply pickFilter with sql language', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), pickFilter: `ServiceName = '${WF_SVC}'`, pickFilterLanguage: 'sql', @@ -455,7 +455,7 @@ describe('MCP Trace Tools', () => { }); it('should auto-pick trace with an error span', async () => { - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: traceSource._id.toString(), pickFilter: 'ServiceName:wf-test-err-svc', pickBy: 'first_error', @@ -498,7 +498,7 @@ describe('MCP Trace Tools', () => { }); const TRACE_ID = 'aaaabbbbccccddddeeeeffffgggghhhh'; - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: noLogTraceSource._id.toString(), traceId: TRACE_ID, includeLogs: true, @@ -538,7 +538,7 @@ describe('MCP Trace Tools', () => { }); const TRACE_ID = 'aaaabbbbccccddddeeeeffffgggghhhh'; - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: badLogTraceSource._id.toString(), traceId: TRACE_ID, includeLogs: true, @@ -579,7 +579,7 @@ describe('MCP Trace Tools', () => { }); const TRACE_ID = 'aaaabbbbccccddddeeeeffffgggghhhh'; - const result = await callTool(client, 'hyperdx_trace_waterfall', { + const result = await callTool(client, 'clickstack_trace_waterfall', { sourceId: wrongKindTraceSource._id.toString(), traceId: TRACE_ID, includeLogs: true, @@ -595,13 +595,13 @@ describe('MCP Trace Tools', () => { }); }); - // ─── hyperdx_trace_top_time_consuming_operations ─────────────────────────── + // ─── clickstack_trace_top_time_consuming_operations ─────────────────────────── - describe('hyperdx_trace_top_time_consuming_operations', () => { + describe('clickstack_trace_top_time_consuming_operations', () => { it('should return error for non-existent source', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: '000000000000000000000000', parentFilter: "ServiceName = 'api-gateway'", @@ -616,7 +616,7 @@ describe('MCP Trace Tools', () => { it('should return error for non-trace source', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: logSource._id.toString(), parentFilter: "ServiceName = 'api-gateway'", @@ -631,7 +631,7 @@ describe('MCP Trace Tools', () => { it('should return error for invalid time range', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: "ServiceName = 'api-gateway'", @@ -646,7 +646,7 @@ describe('MCP Trace Tools', () => { it('should return empty operations when no traces match', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: "ServiceName = 'nonexistent-service'", @@ -753,7 +753,7 @@ describe('MCP Trace Tools', () => { it('should break down child operations ranked by total time', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: `ServiceName = '${PARENT_SVC}' AND SpanName = '${PARENT_OP}'`, @@ -790,7 +790,7 @@ describe('MCP Trace Tools', () => { // Only trace 2 has a parent >= 850ms (900ms) const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: `ServiceName = '${PARENT_SVC}' AND SpanName = '${PARENT_OP}'`, @@ -815,7 +815,7 @@ describe('MCP Trace Tools', () => { it('should respect topN parameter', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: `ServiceName = '${PARENT_SVC}' AND SpanName = '${PARENT_OP}'`, @@ -840,7 +840,7 @@ describe('MCP Trace Tools', () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: `ServiceName = '${PARENT_SVC}' AND SpanName = '${PARENT_OP}'`, @@ -860,7 +860,7 @@ describe('MCP Trace Tools', () => { it('should return error for invalid parentFilter SQL', async () => { const result = await callTool( client, - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { sourceId: traceSource._id.toString(), parentFilter: 'INVALID SQL @@@ SYNTAX', diff --git a/packages/api/src/mcp/mcpServer.ts b/packages/api/src/mcp/mcpServer.ts index 0635db892b..885cc245e2 100644 --- a/packages/api/src/mcp/mcpServer.ts +++ b/packages/api/src/mcp/mcpServer.ts @@ -13,7 +13,7 @@ import { McpContext } from './tools/types'; export function createServer(context: McpContext) { const server = new McpServer({ - name: 'hyperdx', + name: 'clickstack', version: `${CODE_VERSION}-beta`, }); diff --git a/packages/api/src/mcp/prompts/dashboards/content.ts b/packages/api/src/mcp/prompts/dashboards/content.ts index 6ba6f2c437..cb60705ffa 100644 --- a/packages/api/src/mcp/prompts/dashboards/content.ts +++ b/packages/api/src/mcp/prompts/dashboards/content.ts @@ -17,25 +17,25 @@ export function buildCreateDashboardPrompt( ${userContext} ${sourceSummary} -IMPORTANT: Call hyperdx_list_sources first to get source IDs, then hyperdx_describe_source for each source you plan to query. This gives you the full column schema, attribute keys, and sampled values (e.g. SeverityText, StatusCode). The source IDs above are correct, but you need the schema details to write accurate queries. +IMPORTANT: Call clickstack_list_sources first to get source IDs, then clickstack_describe_source for each source you plan to query. This gives you the full column schema, attribute keys, and sampled values (e.g. SeverityText, StatusCode). The source IDs above are correct, but you need the schema details to write accurate queries. == WORKFLOW == -1. Call hyperdx_list_sources to discover source IDs and connection IDs. -2. Call hyperdx_describe_source for each source you plan to query to get column schema, attribute keys, and sampled low-cardinality values (SeverityText, StatusCode, ServiceName, etc.). -3. Call hyperdx_get_dashboard (no id) to list existing dashboards. If any exist, fetch one or two with their id and skim them to pick up local idioms (naming style, time ranges, common groupings) before adding new ones. Skip when the workspace is empty. +1. Call clickstack_list_sources to discover source IDs and connection IDs. +2. Call clickstack_describe_source for each source you plan to query to get column schema, attribute keys, and sampled low-cardinality values (SeverityText, StatusCode, ServiceName, etc.). +3. Call clickstack_get_dashboard (no id) to list existing dashboards. If any exist, fetch one or two with their id and skim them to pick up local idioms (naming style, time ranges, common groupings) before adding new ones. Skip when the workspace is empty. 4. Pick the right source kind for the question. Trace data for request volume / latency / errors. Log data for severity / messages. Metric data for system telemetry. 5. Sketch tiles, THEN group them into 2-4 containers (Overview / Trends / Errors is a sane default for a service-health pattern) before assembling the save payload. Ungrouped tiles render as a flat sprawl that fails the readability test at five or more tiles. -6. Call hyperdx_save_dashboard with the tiles, containers, and dashboard-level filters. -7. Call hyperdx_query_tile on EVERY tile (not just one) to confirm queries return data and the dashboard is not silently degraded. +6. Call clickstack_save_dashboard with the tiles, containers, and dashboard-level filters. +7. Call clickstack_query_tile on EVERY tile (not just one) to confirm queries return data and the dashboard is not silently degraded. == UPDATING AN EXISTING DASHBOARD == -When updating a dashboard (passing the top-level \`id\` to hyperdx_save_dashboard), +When updating a dashboard (passing the top-level \`id\` to clickstack_save_dashboard), the \`filters\` array must be fully self-describing: every filter needs an \`id\`: - Existing filter you are KEEPING: copy its \`id\` verbatim from the - hyperdx_get_dashboard response. The same applies if you are renaming + clickstack_get_dashboard response. The same applies if you are renaming or tweaking it. This preserves any savedFilterValues bound to that id. - New filter you are ADDING in this update: generate a fresh random 24-character hex string (a Mongo-style ObjectId) and set it as \`id\`. @@ -46,11 +46,11 @@ the \`filters\` array must be fully self-describing: every filter needs an \`id\ Recommended pattern: - 1. \`hyperdx_get_dashboard({ id })\` to capture the current \`filters[]\` + 1. \`clickstack_get_dashboard({ id })\` to capture the current \`filters[]\` and their ids. 2. Build the new array: spread the kept filters as-is, append new filters with freshly-minted ids, omit removed ones. - 3. \`hyperdx_save_dashboard({ id, ..., filters: })\`. + 3. \`clickstack_save_dashboard({ id, ..., filters: })\`. == TILE TYPE GUIDE == @@ -72,15 +72,15 @@ Use RAW SQL tiles (with connectionId) only for queries the builder cannot expres - Top-level columns use PascalCase by default: Duration, StatusCode, SpanName, Body, SeverityText, ServiceName, SpanKind. NOTE: These are defaults for the standard HyperDX schema. Custom sources may use different names. - Always call hyperdx_describe_source to get the real column names, keyColumns, and sampled values for each source. + Always call clickstack_describe_source to get the real column names, keyColumns, and sampled values for each source. - Map-type columns use bracket syntax: SpanAttributes['http.method'], ResourceAttributes['service.name']. NEVER use dot notation for Map columns. Always use brackets. - JSON-type columns use dot notation: JsonColumn.key.subkey. - Check the jsType returned by hyperdx_describe_source to determine whether a column is Map or JSON. + Check the jsType returned by clickstack_describe_source to determine whether a column is Map or JSON. == DESIGN CHECKLIST == -Apply these before calling hyperdx_save_dashboard. Each rule is enforced by the renderer; ignoring one is a runtime error you will then need to fix. +Apply these before calling clickstack_save_dashboard. Each rule is enforced by the renderer; ignoring one is a runtime error you will then need to fix. 1. ONE QUESTION PER DASHBOARD. A dashboard answers a single observability question well. Split if the request mixes traces, logs, and metrics into unrelated views. @@ -104,7 +104,7 @@ Apply these before calling hyperdx_save_dashboard. Each rule is enforced by the 9. FOR FOCUSED PER-DIMENSION DASHBOARDS, DECLARE A DASHBOARD-LEVEL FILTER. Pass filters: [{ type: "QUERY_EXPRESSION", name, expression, sourceId }] at the top level. The user gets a dropdown in the dashboard header; by default every tile is re-scoped when a value is picked. On mixed-source dashboards add appliesToSourceIds: ["", ...] to restrict the filter to only the tiles whose source carries that column. Omit the field to keep the broadcast-to-all-tiles default. Do NOT hardcode the dimension into each tile's where clause. -10. UPDATE IS REPLACE, NOT MERGE. hyperdx_save_dashboard with an id overwrites tiles, containers, and filters in their entirety. Call hyperdx_get_dashboard first when you only want to add or rename one entry; do not send a partial set or you will silently drop everything you omitted. +10. UPDATE IS REPLACE, NOT MERGE. clickstack_save_dashboard with an id overwrites tiles, containers, and filters in their entirety. Call clickstack_get_dashboard first when you only want to add or rename one entry; do not send a partial set or you will silently drop everything you omitted. 11. GROUP RELATED TILES INTO CONTAINERS. REQUIRED at five or more tiles, no exceptions. An ungrouped wall of nine or ten tiles is a readability failure even when each tile is correct in isolation. Containers are the right way to introduce structure; markdown tiles for section labels are not. @@ -121,17 +121,17 @@ Apply these before calling hyperdx_save_dashboard. Each rule is enforced by the ] Use tabs when one container needs to show different views of the same data (Throughput / Latency / Errors over time, for instance); the tab bar appears when a container has two or more tabs declared. -12. VALIDATE EVERY TILE AFTER SAVE. After hyperdx_save_dashboard, call hyperdx_query_tile on EVERY tile (not just one). Save validates input shape; it does NOT validate query semantics. Some queries pass save and fail at render time (known gaps: Lucene comparison/wildcard on map attributes, metric tiles with multiple metricTables, malformed having clauses). If query_tile returns an error, fix the tile and re-save before declaring the dashboard ready. +12. VALIDATE EVERY TILE AFTER SAVE. After clickstack_save_dashboard, call clickstack_query_tile on EVERY tile (not just one). Save validates input shape; it does NOT validate query semantics. Some queries pass save and fail at render time (known gaps: Lucene comparison/wildcard on map attributes, metric tiles with multiple metricTables, malformed having clauses). If query_tile returns an error, fix the tile and re-save before declaring the dashboard ready. 13. NO TITLE-RECAP MARKDOWN TILE. The dashboard's name shows in the title bar. Adding a markdown tile with the dashboard name (or a "About this dashboard" header) doubles the title and eats a row of vertical space because markdown heading styles render at title-bar scale. Skip the markdown tile entirely on starter dashboards. == ADAPT, DO NOT COPY == -The dashboard_examples prompt returns concrete example shapes. Read them as patterns to adapt to the user's actual request and schema, not as literal templates to substitute names into. Specifically: the literal "ServiceName", "StatusCode:STATUS_CODE_ERROR", "Duration" come from the standard HyperDX schema. Real source schemas may use different column names and status values; always verify with hyperdx_list_sources before reusing literals. +The dashboard_examples prompt returns concrete example shapes. Read them as patterns to adapt to the user's actual request and schema, not as literal templates to substitute names into. Specifically: the literal "ServiceName", "StatusCode:STATUS_CODE_ERROR", "Duration" come from the standard HyperDX schema. Real source schemas may use different column names and status values; always verify with clickstack_list_sources before reusing literals. IMPORTANT: The exact values for StatusCode and SeverityText vary by deployment. Do NOT assume values like "STATUS_CODE_ERROR", "Ok", "error", or "fatal". -Always call hyperdx_describe_source first and inspect the lowCardinalityValues +Always call clickstack_describe_source first and inspect the lowCardinalityValues returned for each source to discover the real values used in your data. == DEFAULT TIME WINDOW == @@ -143,12 +143,12 @@ Dashboards open with a 15-minute default window. There is no dashboard-level fie - Using valueExpression with aggFn "count" (count takes no valueExpression). - Forgetting valueExpression for non-count aggFns (avg, sum, min, max, quantile all require it). - Using dot notation for Map-type attributes (always use SpanAttributes['key'] bracket syntax). -- Skipping hyperdx_list_sources + hyperdx_describe_source (you need real source IDs, column names, and values). -- Skipping hyperdx_query_tile after save (tiles can silently fail on syntax or attribute mismatches). +- Skipping clickstack_list_sources + clickstack_describe_source (you need real source IDs, column names, and values). +- Skipping clickstack_query_tile after save (tiles can silently fail on syntax or attribute mismatches). - Setting chart-level numberFormat on a table that mixes counts and durations (counts render as 0:00:00). - Multiple select items on number / pie / heatmap tiles (each takes exactly one). - Missing level on aggFn "quantile" (must specify 0.5, 0.9, 0.95, or 0.99). -- Assuming StatusCode or SeverityText values (always inspect lowCardinalityValues from hyperdx_describe_source). +- Assuming StatusCode or SeverityText values (always inspect lowCardinalityValues from clickstack_describe_source). - Heatmap on a non-Trace source (heatmap is Trace-only today). - Hardcoding a focus dimension into every tile's where clause (use a dashboard-level filter instead).`; } @@ -614,7 +614,7 @@ or a more detailed dashboard. Useful when the dashboard answers inside that service?". Replace with the ID of an existing service-detail -dashboard. Use hyperdx_get_dashboard (no id) to list dashboards and +dashboard. Use clickstack_get_dashboard (no id) to list dashboards and copy the right ID and discover which filters are available. { @@ -741,8 +741,8 @@ SQL TEMPLATE REFERENCE: 'Concrete dashboard examples for common observability patterns. ' + 'These are PATTERNS to adapt, not literal templates. Adapt the structure ' + "(table shape, container layout, filter expression) to the user's request; " + - 'always verify column names and status literals via hyperdx_list_sources before reusing them.\n' + - 'Replace ${sourceId} / ${connectionId} placeholders with real IDs from hyperdx_list_sources.\n\n'; + 'always verify column names and status literals via clickstack_list_sources before reusing them.\n' + + 'Replace ${sourceId} / ${connectionId} placeholders with real IDs from clickstack_list_sources.\n\n'; if (pattern) { const key = pattern.toLowerCase().replace(/[\s-]+/g, '_'); @@ -765,7 +765,7 @@ SQL TEMPLATE REFERENCE: } export function buildQueryGuidePrompt(): string { - return `Reference guide for writing queries with HyperDX MCP tools (hyperdx_timeseries, hyperdx_table, hyperdx_search, hyperdx_event_patterns, hyperdx_sql, and hyperdx_save_dashboard). + return `Reference guide for writing queries with ClickStack MCP tools (clickstack_timeseries, clickstack_table, clickstack_search, clickstack_event_patterns, clickstack_sql, and clickstack_save_dashboard). == AGGREGATION FUNCTIONS (aggFn) == @@ -794,7 +794,7 @@ Top-level columns (PascalCase defaults, use directly in valueExpression and grou Duration, StatusCode, SpanName, ServiceName, SpanKind, Body, SeverityText, Timestamp, TraceId, SpanId, ParentSpanId NOTE: These are defaults for the standard HyperDX schema. Custom sources may - use different column names. Always verify with hyperdx_describe_source, which returns + use different column names. Always verify with clickstack_describe_source, which returns the real column names and keyColumns expressions for each source. Map-type columns (bracket syntax, access keys via ['key']): @@ -810,7 +810,7 @@ IMPORTANT: Always use bracket syntax for Map-type columns. Never use dot notatio JSON-type columns (dot notation, access nested keys via dot path): JsonColumn.key.subkey - NOTE: Check the jsType field returned by hyperdx_describe_source to determine + NOTE: Check the jsType field returned by clickstack_describe_source to determine whether a column is Map (use brackets) or JSON (use dots). == LUCENE FILTER SYNTAX == @@ -831,7 +831,7 @@ Used in the "where" field of select items and search tiles. NOTE: In Lucene filters, use dot notation for attribute keys (service.name, http.method). This is different from valueExpression and groupBy which require bracket syntax (SpanAttributes['http.method']). -GOTCHA: Lucene queries that reference map-attribute keys via dotted paths (http.status_code, db.system, deployment.environment, anything under SpanAttributes / ResourceAttributes / LogAttributes) are NOT reliably translated to bracket access. Operators (>=, >, <=, <), wildcards (*), AND simple equality all hit translator gaps depending on the column. Symptoms: tile saves fine; hyperdx_query_tile returns an error or returns zero rows when there should be matches; the dashboard renders "Error loading chart" on the tile. Do NOT trust dotted-path Lucene; use SQL with bracket access for ALL map-attribute filtering. +GOTCHA: Lucene queries that reference map-attribute keys via dotted paths (http.status_code, db.system, deployment.environment, anything under SpanAttributes / ResourceAttributes / LogAttributes) are NOT reliably translated to bracket access. Operators (>=, >, <=, <), wildcards (*), AND simple equality all hit translator gaps depending on the column. Symptoms: tile saves fine; clickstack_query_tile returns an error or returns zero rows when there should be matches; the dashboard renders "Error loading chart" on the tile. Do NOT trust dotted-path Lucene; use SQL with bracket access for ALL map-attribute filtering. Unreliable in Lucene (any operation, any operator): http.status_code:>=500 (operator) @@ -927,7 +927,7 @@ For configType: "sql" tiles, write ClickHouse SQL with template macros: search No select items (select is a column list string). where is the filter. markdown No select items. Set markdown field with content. -NOTE: Authoring builder tiles on a metric source is not reliable today. The MCP select-item shape does not carry the metricName / metricType fields the metric query path needs, and a save with a metric sourceId may render in the UI as "Both table name and UUID are empty" even though the save itself succeeded. For metrics, use a raw SQL tile (configType: "sql") with explicit table reference. The standard tables that back a metric source are otel_metrics_gauge, otel_metrics_sum, and otel_metrics_histogram; hyperdx_list_sources returns the metric source's metricTables map so you know which table holds which metric kind. Discovery: metric source schemas today do NOT publish mapAttributeKeys for ResourceAttributes / Attributes the way log and trace sources do, so attribute keys must be discovered by sampling (SELECT DISTINCT mapKeys(Attributes) FROM ...). +NOTE: Authoring builder tiles on a metric source is not reliable today. The MCP select-item shape does not carry the metricName / metricType fields the metric query path needs, and a save with a metric sourceId may render in the UI as "Both table name and UUID are empty" even though the save itself succeeded. For metrics, use a raw SQL tile (configType: "sql") with explicit table reference. The standard tables that back a metric source are otel_metrics_gauge, otel_metrics_sum, and otel_metrics_histogram; clickstack_list_sources returns the metric source's metricTables map so you know which table holds which metric kind. Discovery: metric source schemas today do NOT publish mapAttributeKeys for ResourceAttributes / Attributes the way log and trace sources do, so attribute keys must be discovered by sampling (SELECT DISTINCT mapKeys(Attributes) FROM ...). == NUMBER FORMAT == @@ -997,11 +997,11 @@ Destination types: { type: "search", target, whereLanguage, whereTemplate?, filters? } Opens the /search page for a log or trace source. Metric and session sources are rejected by the server (the /search page does not render those kinds). { type: "dashboard", target, whereLanguage, whereTemplate?, filters? } - Opens another HyperDX dashboard owned by the same team. + Opens another ClickStack dashboard owned by the same team. Target shape, how the destination is identified: target: { mode: "id", id: "" } - Pins a specific source (for type=search) or dashboard (for type=dashboard) by its ID. Prefer this when the target is known. Find source IDs with hyperdx_list_sources; find dashboard IDs with hyperdx_get_dashboard (no id arg returns the list of all dashboards). + Pins a specific source (for type=search) or dashboard (for type=dashboard) by its ID. Prefer this when the target is known. Find source IDs with clickstack_list_sources; find dashboard IDs with clickstack_get_dashboard (no id arg returns the list of all dashboards). target: { mode: "template", template: "" } Renders the template against the clicked row's columns, then resolves the result by NAME on the destination team. Example template "{{Service}}" reads the row's Service value and looks up a source/dashboard with that name. Use this when the destination depends on which row was clicked. A constant template like "Service Detail" (no Handlebars vars) simply resolves to the dashboard or source with that exact name. @@ -1088,7 +1088,7 @@ For onClick drill-down on a plain top-level column (ServiceName), no workaround == CONTAINERS AND TABS == -Optional dashboard organization layer. Group related tiles visually with optional tab bars. Pass on hyperdx_save_dashboard as a top-level "containers" array; reference containers from tiles via "containerId" (and "tabId" when the container has tabs). +Optional dashboard organization layer. Group related tiles visually with optional tab bars. Pass on clickstack_save_dashboard as a top-level "containers" array; reference containers from tiles via "containerId" (and "tabId" when the container has tabs). Container shape: { id, title, collapsed, collapsible?, bordered?, tabs? } @@ -1112,7 +1112,7 @@ Rules enforced by the API: - A tile's tabId requires containerId to be set. Example: - hyperdx_save_dashboard({ + clickstack_save_dashboard({ name: "Service Overview", containers: [ { @@ -1149,7 +1149,7 @@ containers[i].tabs[j].id). Read the path to know which input to fix. == EVENT PATTERN MINING == -Use hyperdx_event_patterns when asked to find common log messages, recurring +Use clickstack_event_patterns when asked to find common log messages, recurring patterns, noisy services, or top event types. This is the right choice whenever the question is about "most common logs", "top patterns", "what is generating the most log volume", or similar pattern-discovery questions. @@ -1161,7 +1161,7 @@ It samples random events and clusters them with the Drain algorithm, returning: - a whereSnippet for each pattern to drill into matching raw events Required parameters: - - sourceId (call hyperdx_list_sources first) + - sourceId (call clickstack_list_sources first) - startTime / endTime for the time window Optional parameters: @@ -1170,7 +1170,7 @@ Optional parameters: - bodyExpression: column to mine (auto-detected: Body for logs, SpanName for traces) Example: find top patterns for production services over the last 4 hours: - hyperdx_event_patterns({ + clickstack_event_patterns({ sourceId: "", startTime: "<4 hours ago ISO>", endTime: "", @@ -1190,12 +1190,12 @@ Example: find top patterns for production services over the last 4 hours: 3. Using dot notation for Map-type attributes in valueExpression or groupBy Wrong: groupBy: "SpanAttributes.http.method" Correct: groupBy: "SpanAttributes['http.method']" - NOTE: JSON-type columns DO use dot notation. Check jsType from hyperdx_describe_source. + NOTE: JSON-type columns DO use dot notation. Check jsType from clickstack_describe_source. 4. Multiple select items on number / pie / heatmap tiles Wrong: displayType: "number", select: [{ aggFn: "count" }, { aggFn: "avg", ... }] Correct: displayType: "number", select: [{ aggFn: "count" }] - Note: hyperdx_table (the query tool) auto-upgrades shape:"number" to "table" when select has >1 item; dashboard tiles do not. + Note: clickstack_table (the query tool) auto-upgrades shape:"number" to "table" when select has >1 item; dashboard tiles do not. 5. Missing level for quantile Wrong: { aggFn: "quantile", valueExpression: "Duration" } @@ -1212,7 +1212,7 @@ Example: find top patterns for production services over the last 4 hours: The dashboard filter applies globally; tiles do not need the literal. On mixed-source dashboards where only some tiles carry the column, add appliesToSourceIds: ["", ...] to scope the filter to just those sources instead of breaking the unrelated tiles. 8. Forgetting to validate tiles after saving - Always call hyperdx_query_tile on EVERY tile after hyperdx_save_dashboard, not just one. Save validates input shape but not query semantics. Several known gaps (Lucene comparison/wildcard on map attributes, builder tiles on metric sources, malformed having) pass save and fail at render time. A dashboard with one bad tile renders the whole page in a degraded state; the user sees "Error loading chart" with no way to know which tile broke unless you validated. If query_tile returns an error, fix the where / SQL and re-save before declaring the dashboard ready. + Always call clickstack_query_tile on EVERY tile after clickstack_save_dashboard, not just one. Save validates input shape but not query semantics. Several known gaps (Lucene comparison/wildcard on map attributes, builder tiles on metric sources, malformed having) pass save and fail at render time. A dashboard with one bad tile renders the whole page in a degraded state; the user sees "Error loading chart" with no way to know which tile broke unless you validated. If query_tile returns an error, fix the where / SQL and re-save before declaring the dashboard ready. 9. Using sourceId with SQL tiles or connectionId with builder tiles Builder tiles (line, table, etc.) use sourceId. @@ -1220,13 +1220,13 @@ Example: find top patterns for production services over the last 4 hours: 10. Assuming StatusCode or SeverityText values Values like STATUS_CODE_ERROR, Ok, error, fatal vary by deployment. - Always call hyperdx_describe_source and inspect the lowCardinalityValues + Always call clickstack_describe_source and inspect the lowCardinalityValues before writing filters that depend on these columns. 11. Heatmap tile on a non-Trace source Wrong: { displayType: "heatmap", sourceId: "", select: [...] } Correct: { displayType: "heatmap", sourceId: "", select: [...] } - Heatmap is currently restricted to Trace sources. Pick a source whose kind is "trace" from hyperdx_list_sources. + Heatmap is currently restricted to Trace sources. Pick a source whose kind is "trace" from clickstack_list_sources. 12. Heatmap tile with empty or missing valueExpression Wrong: select: [{}] diff --git a/packages/api/src/mcp/prompts/dashboards/helpers.ts b/packages/api/src/mcp/prompts/dashboards/helpers.ts index 364f2a9407..f0c4e3d470 100644 --- a/packages/api/src/mcp/prompts/dashboards/helpers.ts +++ b/packages/api/src/mcp/prompts/dashboards/helpers.ts @@ -5,7 +5,7 @@ export function buildSourceSummary( connections: { _id: unknown; name: string }[], ): string { if (sources.length === 0 && connections.length === 0) { - return 'No sources or connections found. Call hyperdx_list_sources to discover available data.'; + return 'No sources or connections found. Call clickstack_list_sources to discover available data.'; } const lines: string[] = []; diff --git a/packages/api/src/mcp/prompts/dashboards/index.ts b/packages/api/src/mcp/prompts/dashboards/index.ts index e98e9f312d..a8735ee33e 100644 --- a/packages/api/src/mcp/prompts/dashboards/index.ts +++ b/packages/api/src/mcp/prompts/dashboards/index.ts @@ -26,7 +26,7 @@ const dashboardPrompts: PromptDefinition = (server, context) => { { title: 'Create a Dashboard', description: - 'Create a HyperDX dashboard with the MCP tools. ' + + 'Create a ClickStack dashboard with the MCP tools. ' + 'Follow the recommended workflow, pick tile types, write queries, ' + 'and validate results against your real data sources.', argsSchema: { @@ -72,7 +72,7 @@ const dashboardPrompts: PromptDefinition = (server, context) => { 'Failed to fetch sources for create_dashboard prompt', ); sourceSummary = - 'Could not fetch sources. Call hyperdx_list_sources to discover available data.'; + 'Could not fetch sources. Call clickstack_list_sources to discover available data.'; traceSourceId = ''; logSourceId = ''; } @@ -106,7 +106,7 @@ const dashboardPrompts: PromptDefinition = (server, context) => { 'Concrete dashboard example shapes for common observability patterns: ' + 'service_inventory, service_detail, log_analytics, backend_dependencies, drilldown_links, infrastructure_sql. ' + 'Each example carries a "when to use" header. Adapt the structure to the user\'s request; ' + - 'do not copy literal column names or status values without verifying via hyperdx_list_sources first.', + 'do not copy literal column names or status values without verifying via clickstack_list_sources first.', argsSchema: { pattern: z .string() diff --git a/packages/api/src/mcp/tools/alerts/getAlert.ts b/packages/api/src/mcp/tools/alerts/getAlert.ts index b6059ebd7b..6b39655d93 100644 --- a/packages/api/src/mcp/tools/alerts/getAlert.ts +++ b/packages/api/src/mcp/tools/alerts/getAlert.ts @@ -50,7 +50,7 @@ export function registerGetAlert(server: McpServer, context: McpContext): void { const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_get_alert', + 'clickstack_get_alert', { title: 'Get Alert(s)', description: @@ -75,7 +75,7 @@ export function registerGetAlert(server: McpServer, context: McpContext): void { ), }), }, - withToolTracing('hyperdx_get_alert', context, async ({ id, state }) => { + withToolTracing('clickstack_get_alert', context, async ({ id, state }) => { // ── List all alerts (slim summary) ── if (!id) { const query: Record = { diff --git a/packages/api/src/mcp/tools/alerts/getWebhook.ts b/packages/api/src/mcp/tools/alerts/getWebhook.ts index e87db90fb7..0707b0c1ab 100644 --- a/packages/api/src/mcp/tools/alerts/getWebhook.ts +++ b/packages/api/src/mcp/tools/alerts/getWebhook.ts @@ -13,16 +13,16 @@ export function registerGetWebhook( const { teamId } = context; server.registerTool( - 'hyperdx_get_webhook', + 'clickstack_get_webhook', { title: 'List Webhooks', description: 'List available webhook destinations (id, name, service type). ' + 'Use the returned id as the webhookId when creating alerts with ' + - 'hyperdx_save_alert.', + 'clickstack_save_alert.', inputSchema: z.object({}), }, - withToolTracing('hyperdx_get_webhook', context, async () => { + withToolTracing('clickstack_get_webhook', context, async () => { const webhooks = await Webhook.find({ team: teamId }); const output = webhooks.map(wh => ({ diff --git a/packages/api/src/mcp/tools/alerts/saveAlert.ts b/packages/api/src/mcp/tools/alerts/saveAlert.ts index c693858b8e..88e9b7635c 100644 --- a/packages/api/src/mcp/tools/alerts/saveAlert.ts +++ b/packages/api/src/mcp/tools/alerts/saveAlert.ts @@ -40,7 +40,7 @@ export function registerSaveAlert( const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_save_alert', + 'clickstack_save_alert', { title: 'Create or Update Alert', description: @@ -49,7 +49,7 @@ export function registerSaveAlert( 'metric crosses a threshold. A webhook notification channel is required.', inputSchema: mcpSaveAlertSchema, }, - withToolTracing('hyperdx_save_alert', context, async input => { + withToolTracing('clickstack_save_alert', context, async input => { // ── Runtime cross-field validation ── const validationError = validateSaveAlertInput(input); if (validationError) { diff --git a/packages/api/src/mcp/tools/alerts/schemas.ts b/packages/api/src/mcp/tools/alerts/schemas.ts index d5878e9847..c678863430 100644 --- a/packages/api/src/mcp/tools/alerts/schemas.ts +++ b/packages/api/src/mcp/tools/alerts/schemas.ts @@ -6,7 +6,7 @@ import { import { z } from 'zod'; // --------------------------------------------------------------------------- -// MCP-compatible flat Zod schema for hyperdx_save_alert. +// MCP-compatible flat Zod schema for clickstack_save_alert. // // The MCP SDK's normalizeObjectSchema() cannot serialize ZodEffects // (superRefine) or discriminatedUnion. We keep the inputSchema as a plain diff --git a/packages/api/src/mcp/tools/dashboards/deleteDashboard.ts b/packages/api/src/mcp/tools/dashboards/deleteDashboard.ts index 5d9fb48b78..0b00f6a4b1 100644 --- a/packages/api/src/mcp/tools/dashboards/deleteDashboard.ts +++ b/packages/api/src/mcp/tools/dashboards/deleteDashboard.ts @@ -15,18 +15,18 @@ export function registerDeleteDashboard( const { teamId } = context; server.registerTool( - 'hyperdx_delete_dashboard', + 'clickstack_delete_dashboard', { title: 'Delete Dashboard', description: 'Permanently delete a dashboard by ID. Also removes any alerts attached to its tiles. ' + - 'Use hyperdx_get_dashboard (without an ID) to list available dashboard IDs.', + 'Use clickstack_get_dashboard (without an ID) to list available dashboard IDs.', inputSchema: z.object({ id: z.string().describe('Dashboard ID to delete.'), }), }, withToolTracing( - 'hyperdx_delete_dashboard', + 'clickstack_delete_dashboard', context, async ({ id: dashboardId }) => { if (!mongoose.Types.ObjectId.isValid(dashboardId)) { diff --git a/packages/api/src/mcp/tools/dashboards/getDashboard.ts b/packages/api/src/mcp/tools/dashboards/getDashboard.ts index ee9b2d73a8..412b9e17b3 100644 --- a/packages/api/src/mcp/tools/dashboards/getDashboard.ts +++ b/packages/api/src/mcp/tools/dashboards/getDashboard.ts @@ -18,7 +18,7 @@ export function registerGetDashboard( const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_get_dashboard', + 'clickstack_get_dashboard', { title: 'Get Dashboard(s)', description: @@ -33,7 +33,7 @@ export function registerGetDashboard( ), }), }, - withToolTracing('hyperdx_get_dashboard', context, async ({ id }) => { + withToolTracing('clickstack_get_dashboard', context, async ({ id }) => { if (!id) { const dashboards = await getDashboards( new mongoose.Types.ObjectId(teamId), diff --git a/packages/api/src/mcp/tools/dashboards/queryTile.ts b/packages/api/src/mcp/tools/dashboards/queryTile.ts index 5604bee8df..77b12df45b 100644 --- a/packages/api/src/mcp/tools/dashboards/queryTile.ts +++ b/packages/api/src/mcp/tools/dashboards/queryTile.ts @@ -16,21 +16,21 @@ export function registerQueryTile( const { teamId } = context; server.registerTool( - 'hyperdx_query_tile', + 'clickstack_query_tile', { title: 'Query a Dashboard Tile', description: 'Execute the query for a specific tile on an existing dashboard. ' + 'Useful for validating that a tile returns data or for spot-checking results ' + 'without rebuilding the query from scratch. ' + - 'Use hyperdx_get_dashboard with an ID to find tile IDs.', + 'Use clickstack_get_dashboard with an ID to find tile IDs.', inputSchema: z.object({ dashboardId: z.string().describe('Dashboard ID.'), tileId: z .string() .describe( 'Tile ID within the dashboard. ' + - 'Obtain from hyperdx_get_dashboard.', + 'Obtain from clickstack_get_dashboard.', ), startTime: z .string() @@ -46,7 +46,7 @@ export function registerQueryTile( }), }, withToolTracing( - 'hyperdx_query_tile', + 'clickstack_query_tile', context, async ({ dashboardId, tileId, startTime, endTime }) => { const timeRange = parseTimeRange(startTime, endTime); diff --git a/packages/api/src/mcp/tools/dashboards/saveDashboard.ts b/packages/api/src/mcp/tools/dashboards/saveDashboard.ts index d6bee8eb5c..4e118c6212 100644 --- a/packages/api/src/mcp/tools/dashboards/saveDashboard.ts +++ b/packages/api/src/mcp/tools/dashboards/saveDashboard.ts @@ -41,13 +41,13 @@ export function registerSaveDashboard( const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_save_dashboard', + 'clickstack_save_dashboard', { title: 'Create or Update Dashboard', description: 'Create a new dashboard (omit id) or update an existing one (provide id). ' + - 'Call hyperdx_list_sources first to obtain sourceId and connectionId values. ' + - 'IMPORTANT: After saving a dashboard, always run hyperdx_query_tile on each tile ' + + 'Call clickstack_list_sources first to obtain sourceId and connectionId values. ' + + 'IMPORTANT: After saving a dashboard, always run clickstack_query_tile on each tile ' + 'to confirm the queries work and return expected data. Tiles can silently fail ' + 'due to incorrect filter syntax, missing attributes, or wrong column names.', inputSchema: z.object({ @@ -65,7 +65,7 @@ export function registerSaveDashboard( }), }, withToolTracing( - 'hyperdx_save_dashboard', + 'clickstack_save_dashboard', context, async ({ id: dashboardId, @@ -311,7 +311,7 @@ async function createDashboard({ ...(frontendUrl ? { url: `${frontendUrl}/dashboards/${newDashboard._id}` } : {}), - hint: 'Use hyperdx_query_tile to test individual tile queries before viewing the dashboard.', + hint: 'Use clickstack_query_tile to test individual tile queries before viewing the dashboard.', }, null, 2, @@ -570,7 +570,7 @@ async function updateDashboard({ ...(frontendUrl ? { url: `${frontendUrl}/dashboards/${updatedDashboard._id}` } : {}), - hint: 'Use hyperdx_query_tile to test individual tile queries before viewing the dashboard.', + hint: 'Use clickstack_query_tile to test individual tile queries before viewing the dashboard.', }, null, 2, diff --git a/packages/api/src/mcp/tools/dashboards/schemas.ts b/packages/api/src/mcp/tools/dashboards/schemas.ts index 8a0fc5e8d1..fc692acbb7 100644 --- a/packages/api/src/mcp/tools/dashboards/schemas.ts +++ b/packages/api/src/mcp/tools/dashboards/schemas.ts @@ -1,6 +1,6 @@ // prose-lint: allow-file // MCP tool descriptions intentionally use the established en-dash -// separator (e.g. "Source ID – call hyperdx_list_sources") for LLM +// separator (e.g. "Source ID – call clickstack_list_sources") for LLM // readability. Reformatting all separators is out of scope here. import { AggregateFunctionSchema, @@ -174,8 +174,8 @@ const mcpOnClickTargetSchema = z .min(1) .describe( 'Concrete source ID (for type=search) or dashboard ID (for type=dashboard). ' + - 'Get source IDs from hyperdx_list_sources; get dashboard IDs from ' + - 'hyperdx_get_dashboard (no id arg returns the list). ' + + 'Get source IDs from clickstack_list_sources; get dashboard IDs from ' + + 'clickstack_get_dashboard (no id arg returns the list). ' + 'For type=search the source kind must be "log" or "trace"; the /search ' + 'page does not render metric/session sources.', ), @@ -245,7 +245,9 @@ const mcpOnClickSearchSchema = z const mcpOnClickDashboardSchema = z .object({ - type: z.literal('dashboard').describe('Link to another HyperDX dashboard.'), + type: z + .literal('dashboard') + .describe('Link to another ClickStack dashboard.'), target: mcpOnClickTargetSchema, whereTemplate: z .string() @@ -366,7 +368,7 @@ const mcpTileLayoutSchema = z.object({ const mcpLineTileSchema = mcpTileLayoutSchema.extend({ config: z.object({ displayType: z.literal('line').describe('Line chart over time'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), select: z .array(mcpTileSelectItemSchema) .min(1) @@ -397,7 +399,7 @@ const mcpBarTileSchema = mcpTileLayoutSchema.extend({ displayType: z .literal('stacked_bar') .describe('Stacked bar chart over time'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), select: z.array(mcpTileSelectItemSchema).min(1).max(20), groupBy: z.string().optional(), fillNulls: z.boolean().optional().default(true), @@ -409,7 +411,7 @@ const mcpBarTileSchema = mcpTileLayoutSchema.extend({ const mcpTableTileSchema = mcpTileLayoutSchema.extend({ config: z.object({ displayType: z.literal('table').describe('Tabular aggregated data'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), select: z.array(mcpTileSelectItemSchema).min(1).max(20), groupBy: z .string() @@ -444,7 +446,7 @@ const mcpTableTileSchema = mcpTileLayoutSchema.extend({ const mcpNumberTileSchema = mcpTileLayoutSchema.extend({ config: z.object({ displayType: z.literal('number').describe('Single aggregate scalar value'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), select: z .array(mcpTileSelectItemSchema) .length(1) @@ -461,7 +463,7 @@ const mcpNumberTileSchema = mcpTileLayoutSchema.extend({ const mcpPieTileSchema = mcpTileLayoutSchema.extend({ config: z.object({ displayType: z.literal('pie').describe('Pie chart'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), select: z.array(mcpTileSelectItemSchema).length(1), groupBy: z .string() @@ -512,7 +514,7 @@ const mcpHeatmapTileSchema = mcpTileLayoutSchema.extend({ sourceId: z .string() .describe( - 'Source ID. Must be a Trace source today; use hyperdx_list_sources and ' + + 'Source ID. Must be a Trace source today; use clickstack_list_sources and ' + 'pick one whose kind is "trace".', ), select: z @@ -539,7 +541,7 @@ const mcpHeatmapTileSchema = mcpTileLayoutSchema.extend({ const mcpSearchTileSchema = mcpTileLayoutSchema.extend({ config: z.object({ displayType: z.literal('search').describe('Log/event search results list'), - sourceId: z.string().describe('Source ID – call hyperdx_list_sources'), + sourceId: z.string().describe('Source ID – call clickstack_list_sources'), where: z .string() .optional() @@ -577,7 +579,7 @@ const mcpSqlTileSchema = mcpTileLayoutSchema.extend({ connectionId: z .string() .describe( - 'Connection ID (not sourceId) – call hyperdx_list_sources to find available connections', + 'Connection ID (not sourceId) – call clickstack_list_sources to find available connections', ), sqlTemplate: z .string() @@ -614,7 +616,7 @@ export const mcpTilesParam = z .array(mcpTileSchema) .describe( 'Array of dashboard tiles. Each tile needs a name, optional layout (x/y/w/h), and a config block. ' + - 'The config block varies by displayType – use hyperdx_list_sources for sourceId and connectionId values.\n\n' + + 'The config block varies by displayType – use clickstack_list_sources for sourceId and connectionId values.\n\n' + 'Example tiles:\n' + '1. Line chart: { "name": "Error Rate", "config": { "displayType": "line", "sourceId": "", ' + '"groupBy": "ResourceAttributes[\'service.name\']", "select": [{ "aggFn": "count", "where": "StatusCode:STATUS_CODE_ERROR" }] } }\n' + @@ -640,7 +642,7 @@ const mcpDashboardFilterSchema = z .describe( 'Filter identity. ' + 'On UPDATE of an existing dashboard, every filter in the array MUST carry ' + - 'an id: pass the exact id returned by hyperdx_get_dashboard for any filter ' + + 'an id: pass the exact id returned by clickstack_get_dashboard for any filter ' + 'you are keeping (so saved values bound to it stay attached), and generate ' + 'a fresh random hex/ObjectId string for any filter you are adding in this ' + 'update. Omitting `id` on an existing filter would orphan its saved values; ' + @@ -670,7 +672,7 @@ const mcpDashboardFilterSchema = z ), sourceId: objectIdSchema.describe( 'Source the filter values are pulled from (for the dropdown). ' + - 'Get IDs from hyperdx_list_sources.', + 'Get IDs from clickstack_list_sources.', ), sourceMetricType: z .nativeEnum(MetricsDataType) diff --git a/packages/api/src/mcp/tools/query/eventDeltas.ts b/packages/api/src/mcp/tools/query/eventDeltas.ts index f5ded14674..7ffdca1570 100644 --- a/packages/api/src/mcp/tools/query/eventDeltas.ts +++ b/packages/api/src/mcp/tools/query/eventDeltas.ts @@ -47,7 +47,7 @@ const deltasSchema = z.object({ .string() .describe( 'Source ID. Works for trace and log sources. ' + - 'Call hyperdx_list_sources for available sources.', + 'Call clickstack_list_sources for available sources.', ), target: groupSchema.describe( 'Target ("outlier") group — the rows you suspect are different (e.g. the ' + @@ -140,7 +140,7 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_event_deltas', + 'clickstack_event_deltas', { title: 'Compare Events: Target vs Baseline', description: @@ -177,9 +177,9 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { ' Any pair of row sets over the same source works — the tool just ' + ' asks "what is statistically different about target vs baseline".\n\n' + 'WHEN NOT TO USE: when the question is already known to be about a ' + - 'specific attribute (use hyperdx_table groupBy), when you want raw ' + - 'rows (use hyperdx_search), or when you need a time-series shape ' + - '(use hyperdx_timeseries).\n\n' + + 'specific attribute (use clickstack_table groupBy), when you want raw ' + + 'rows (use clickstack_search), or when you need a time-series shape ' + + '(use clickstack_timeseries).\n\n' + 'OUTPUT SHAPE: an array of properties, each with rank, key, score, ' + 'semanticBoost (true for well-known OTel attrs like service.name / ' + 'http.method / error.type / status), targetCount and baselineCount ' + @@ -197,7 +197,7 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { inputSchema: deltasSchema, }, withToolTracing( - 'hyperdx_event_deltas', + 'clickstack_event_deltas', context, async (input: DeltasInput) => { const targetRange = parseTimeRange( @@ -272,7 +272,7 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source not found: ${input.sourceId}. Call hyperdx_list_sources to find available source IDs.`, + text: `Source not found: ${input.sourceId}. Call clickstack_list_sources to find available source IDs.`, }, ], }; @@ -286,7 +286,7 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source ${input.sourceId} is kind="${source.kind}". hyperdx_event_deltas requires a trace or log source.`, + text: `Source ${input.sourceId} is kind="${source.kind}". clickstack_event_deltas requires a trace or log source.`, }, ], }; @@ -511,7 +511,7 @@ export function registerEventDeltas(server: McpServer, context: McpContext) { ? { warning: 'One or both groups returned 0 rows — verify the time ' + - 'window and where filter via hyperdx_search.', + 'window and where filter via clickstack_search.', } : {}), }, diff --git a/packages/api/src/mcp/tools/query/eventPatterns.ts b/packages/api/src/mcp/tools/query/eventPatterns.ts index c3da47cca7..a2a85f3d0f 100644 --- a/packages/api/src/mcp/tools/query/eventPatterns.ts +++ b/packages/api/src/mcp/tools/query/eventPatterns.ts @@ -66,7 +66,7 @@ export function registerEventPatterns(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_event_patterns', + 'clickstack_event_patterns', { title: 'Event Pattern Mining', description: @@ -76,15 +76,15 @@ export function registerEventPatterns(server: McpServer, context: McpContext) { 'Use this when asked about "top patterns", "common logs", "noisy services", ' + '"recurring messages", or log noise analysis.\n\n' + 'Each pattern includes a "whereSnippet" — use it as the "where" parameter in ' + - 'a follow-up hyperdx_search call to browse matching raw events.\n\n' + - 'Requires sourceId — call hyperdx_list_sources then hyperdx_describe_source first.\n\n' + + 'a follow-up clickstack_search call to browse matching raw events.\n\n' + + 'Requires sourceId — call clickstack_list_sources then clickstack_describe_source first.\n\n' + 'When to use which tool:\n' + - ' - hyperdx_event_patterns: clustering / recurring shapes / noise analysis\n' + - ' - hyperdx_search: raw individual rows\n' + - ' - hyperdx_table: aggregated metrics / counts / top-N', + ' - clickstack_event_patterns: clustering / recurring shapes / noise analysis\n' + + ' - clickstack_search: raw individual rows\n' + + ' - clickstack_table: aggregated metrics / counts / top-N', inputSchema: eventPatternsSchema, }, - withToolTracing('hyperdx_event_patterns', context, async input => { + withToolTracing('clickstack_event_patterns', context, async input => { const timeRange = parseTimeRange(input.startTime, input.endTime); if ('error' in timeRange) { return { diff --git a/packages/api/src/mcp/tools/query/helpers.ts b/packages/api/src/mcp/tools/query/helpers.ts index 51f83f2d80..7fbd3d200a 100644 --- a/packages/api/src/mcp/tools/query/helpers.ts +++ b/packages/api/src/mcp/tools/query/helpers.ts @@ -216,7 +216,7 @@ export async function runConfigTile( content: [ { type: 'text' as const, - text: `Source not found: ${builderConfig.source}. Call hyperdx_list_sources to discover available source IDs.`, + text: `Source not found: ${builderConfig.source}. Call clickstack_list_sources to discover available source IDs.`, }, ], }; @@ -339,7 +339,7 @@ export async function runConfigTile( content: [ { type: 'text' as const, - text: `Connection not found: ${savedConfig.connection}. Call hyperdx_list_sources to discover available connection IDs.`, + text: `Connection not found: ${savedConfig.connection}. Call clickstack_list_sources to discover available connection IDs.`, }, ], }; diff --git a/packages/api/src/mcp/tools/query/runEventPatterns.ts b/packages/api/src/mcp/tools/query/runEventPatterns.ts index 1e11df8a37..47f757cea6 100644 --- a/packages/api/src/mcp/tools/query/runEventPatterns.ts +++ b/packages/api/src/mcp/tools/query/runEventPatterns.ts @@ -71,7 +71,7 @@ export async function runEventPatterns( content: [ { type: 'text' as const, - text: `Source not found: ${sourceId}. Call hyperdx_list_sources to discover available source IDs.`, + text: `Source not found: ${sourceId}. Call clickstack_list_sources to discover available source IDs.`, }, ], }; @@ -88,7 +88,7 @@ export async function runEventPatterns( content: [ { type: 'text' as const, - text: `Connection not found for source: ${sourceId}. Call hyperdx_list_sources to discover available source IDs.`, + text: `Connection not found for source: ${sourceId}. Call clickstack_list_sources to discover available source IDs.`, }, ], }; @@ -286,7 +286,7 @@ export async function runEventPatterns( // ── Format response ── // Convert trend timestamps to ISO strings, extract sample body texts, // and build a whereSnippet per pattern so the agent can drill into - // matching events via a follow-up hyperdx_search query. + // matching events via a follow-up clickstack_search query. const sampledCount = sampleRows.length; const slicedPatterns = rawPatterns.slice(0, topN); @@ -349,7 +349,7 @@ export async function runEventPatterns( (trendBuckets > 0 ? 'trend.count is similarly extrapolated from sample bucket counts. ' : '') + - 'Use whereSnippet as the "where" parameter in a hyperdx_search call to browse matching raw events.', + 'Use whereSnippet as the "where" parameter in a clickstack_search call to browse matching raw events.', }; const { data: trimmedOutput, isTrimmed } = trimToolResponse(output); diff --git a/packages/api/src/mcp/tools/query/schemas.ts b/packages/api/src/mcp/tools/query/schemas.ts index 217a84c30d..5fe673da7f 100644 --- a/packages/api/src/mcp/tools/query/schemas.ts +++ b/packages/api/src/mcp/tools/query/schemas.ts @@ -125,7 +125,7 @@ export const endTimeSchema = z export const sourceIdSchema = z .string() .describe( - 'Source ID (required). Call hyperdx_list_sources to find available sources.', + 'Source ID (required). Call clickstack_list_sources to find available sources.', ); export const whereSchema = z diff --git a/packages/api/src/mcp/tools/query/search.ts b/packages/api/src/mcp/tools/query/search.ts index 272a757a54..624639ffd7 100644 --- a/packages/api/src/mcp/tools/query/search.ts +++ b/packages/api/src/mcp/tools/query/search.ts @@ -46,21 +46,21 @@ export function registerSearch(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_search', + 'clickstack_search', { title: 'Search Events', description: 'Browse individual log/event/trace rows. ' + 'Use this when you need to see raw events, investigate specific log lines, ' + 'or drill into individual records matching a filter.\n\n' + - 'Requires sourceId — call hyperdx_list_sources then hyperdx_describe_source first.\n\n' + - 'For aggregated metrics, use hyperdx_table instead. ' + - 'For pattern discovery, use hyperdx_event_patterns instead.\n\n' + + 'Requires sourceId — call clickstack_list_sources then clickstack_describe_source first.\n\n' + + 'For aggregated metrics, use clickstack_table instead. ' + + 'For pattern discovery, use clickstack_event_patterns instead.\n\n' + 'Column naming: top-level columns are PascalCase (Duration, StatusCode). ' + "Map attributes use bracket syntax: SpanAttributes['http.method'].", inputSchema: searchSchema, }, - withToolTracing('hyperdx_search', context, async input => { + withToolTracing('clickstack_search', context, async input => { const timeRange = parseTimeRange(input.startTime, input.endTime); if ('error' in timeRange) { return { diff --git a/packages/api/src/mcp/tools/query/sql.ts b/packages/api/src/mcp/tools/query/sql.ts index c6a096675c..4e7fede713 100644 --- a/packages/api/src/mcp/tools/query/sql.ts +++ b/packages/api/src/mcp/tools/query/sql.ts @@ -13,7 +13,7 @@ const sqlSchema = z.object({ .string() .describe( 'Connection ID (required). This is the only split tool that needs a connectionId ' + - 'instead of sourceId. Call hyperdx_list_sources to find available connections.', + 'instead of sourceId. Call clickstack_list_sources to find available connections.', ), sql: z .string() @@ -56,23 +56,23 @@ export function registerSql(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_sql', + 'clickstack_sql', { title: 'Raw SQL Query', description: 'Execute raw ClickHouse SQL. ' + 'ADVANCED: only use this when you need capabilities the builder tools cannot express — ' + 'JOINs, sub-queries, CTEs, or querying tables not registered as sources.\n\n' + - 'Requires connectionId (not sourceId) — call hyperdx_list_sources to find connections. ' + - 'Call hyperdx_describe_source to discover column names before writing SQL.\n\n' + + 'Requires connectionId (not sourceId) — call clickstack_list_sources to find connections. ' + + 'Call clickstack_describe_source to discover column names before writing SQL.\n\n' + 'Results are always returned as table rows — for time-series semantics, ' + 'include a time column and ORDER BY it in your SQL.\n\n' + - 'For standard aggregations use hyperdx_table. ' + - 'For time-series charts use hyperdx_timeseries. ' + - 'For browsing rows use hyperdx_search.', + 'For standard aggregations use clickstack_table. ' + + 'For time-series charts use clickstack_timeseries. ' + + 'For browsing rows use clickstack_search.', inputSchema: sqlSchema, }, - withToolTracing('hyperdx_sql', context, async input => { + withToolTracing('clickstack_sql', context, async input => { const timeRange = parseTimeRange(input.startTime, input.endTime); if ('error' in timeRange) { return { diff --git a/packages/api/src/mcp/tools/query/table.ts b/packages/api/src/mcp/tools/query/table.ts index cf1e15cb03..41b7e44f0b 100644 --- a/packages/api/src/mcp/tools/query/table.ts +++ b/packages/api/src/mcp/tools/query/table.ts @@ -154,14 +154,14 @@ export function registerTable(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_table', + 'clickstack_table', { title: 'Aggregation Table', description: 'Compute aggregated metrics as a table, single number, or pie chart. ' + 'Use this for grouped aggregations, top-N queries, single-value KPIs, ' + 'or proportional breakdowns.\n\n' + - 'Requires sourceId — call hyperdx_list_sources then hyperdx_describe_source first.\n\n' + + 'Requires sourceId — call clickstack_list_sources then clickstack_describe_source first.\n\n' + 'Use the top-level "where" to scope the entire query (e.g. filter by service). ' + 'Each select item can also have its own "where" for per-metric cohort ' + 'comparisons (compiles to If(...)). Both can be used together.\n\n' + @@ -173,7 +173,7 @@ export function registerTable(server: McpServer, context: McpContext) { 'it is transparently upgraded to "table".', inputSchema: tableSchema, }, - withToolTracing('hyperdx_table', context, async input => { + withToolTracing('clickstack_table', context, async input => { const timeRange = parseTimeRange(input.startTime, input.endTime); if ('error' in timeRange) { return { diff --git a/packages/api/src/mcp/tools/query/timeseries.ts b/packages/api/src/mcp/tools/query/timeseries.ts index 190ae2f58c..283effb48a 100644 --- a/packages/api/src/mcp/tools/query/timeseries.ts +++ b/packages/api/src/mcp/tools/query/timeseries.ts @@ -69,20 +69,20 @@ export function registerTimeseries(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_timeseries', + 'clickstack_timeseries', { title: 'Time-Series Chart', description: 'Plot metrics over time as a line or stacked bar chart. ' + 'Use this when you need to visualize trends, compare time-series, ' + 'or monitor metric changes over a time window.\n\n' + - 'Requires sourceId — call hyperdx_list_sources then hyperdx_describe_source first. ' + + 'Requires sourceId — call clickstack_list_sources then clickstack_describe_source first. ' + 'Each select item defines one plotted series.\n\n' + 'Column naming: top-level columns are PascalCase (Duration, StatusCode). ' + "Map attributes use bracket syntax: SpanAttributes['http.method'].", inputSchema: timeseriesSchema, }, - withToolTracing('hyperdx_timeseries', context, async input => { + withToolTracing('clickstack_timeseries', context, async input => { const timeRange = parseTimeRange(input.startTime, input.endTime); if ('error' in timeRange) { return { diff --git a/packages/api/src/mcp/tools/savedSearches/getSavedSearch.ts b/packages/api/src/mcp/tools/savedSearches/getSavedSearch.ts index 90d33bb18b..663566c691 100644 --- a/packages/api/src/mcp/tools/savedSearches/getSavedSearch.ts +++ b/packages/api/src/mcp/tools/savedSearches/getSavedSearch.ts @@ -17,7 +17,7 @@ export function registerGetSavedSearch( const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_get_saved_search', + 'clickstack_get_saved_search', { title: 'Get Saved Search(es)', description: @@ -34,7 +34,7 @@ export function registerGetSavedSearch( ), }), }, - withToolTracing('hyperdx_get_saved_search', context, async ({ id }) => { + withToolTracing('clickstack_get_saved_search', context, async ({ id }) => { // ── List all saved searches (slim query — only fetch the fields we need) ── if (!id) { const savedSearches = await SavedSearch.find( diff --git a/packages/api/src/mcp/tools/savedSearches/saveSavedSearch.ts b/packages/api/src/mcp/tools/savedSearches/saveSavedSearch.ts index e348eb21ff..b8f81020c2 100644 --- a/packages/api/src/mcp/tools/savedSearches/saveSavedSearch.ts +++ b/packages/api/src/mcp/tools/savedSearches/saveSavedSearch.ts @@ -21,16 +21,16 @@ export function registerSaveSavedSearch( const frontendUrl = config.FRONTEND_URL; server.registerTool( - 'hyperdx_save_saved_search', + 'clickstack_save_saved_search', { title: 'Create or Update Saved Search', description: 'Create a new saved search (omit id) or update an existing one (provide id). ' + 'A saved search stores a reusable query against a data source. ' + - 'Use hyperdx_list_sources to find the sourceId.', + 'Use clickstack_list_sources to find the sourceId.', inputSchema: mcpSaveSavedSearchSchema, }, - withToolTracing('hyperdx_save_saved_search', context, async input => { + withToolTracing('clickstack_save_saved_search', context, async input => { const isUpdate = !!input.id; // ── Validate ID for updates ── diff --git a/packages/api/src/mcp/tools/savedSearches/schemas.ts b/packages/api/src/mcp/tools/savedSearches/schemas.ts index d3348be3d9..bd5bbccbb3 100644 --- a/packages/api/src/mcp/tools/savedSearches/schemas.ts +++ b/packages/api/src/mcp/tools/savedSearches/schemas.ts @@ -60,7 +60,7 @@ export const mcpSaveSavedSearchSchema = z.object({ sourceId: z .string() .describe( - 'Source ID — call hyperdx_list_sources to find available sources.', + 'Source ID — call clickstack_list_sources to find available sources.', ), tags: z .array(z.string()) diff --git a/packages/api/src/mcp/tools/sources/describeSource.ts b/packages/api/src/mcp/tools/sources/describeSource.ts index 8f2fefd5ee..a8249ae329 100644 --- a/packages/api/src/mcp/tools/sources/describeSource.ts +++ b/packages/api/src/mcp/tools/sources/describeSource.ts @@ -44,7 +44,7 @@ async function describeSourceSchema( content: [ { type: 'text' as const, - text: `Source "${sourceId}" not found. Call hyperdx_list_sources to see available source IDs.`, + text: `Source "${sourceId}" not found. Call clickstack_list_sources to see available source IDs.`, }, ], }; @@ -103,7 +103,7 @@ async function describeSourceSchema( { source: meta, nextSteps: { - query: `Use hyperdx_timeseries, hyperdx_table, or hyperdx_search with sourceId "${sourceId}" and the metric tables above.`, + query: `Use clickstack_timeseries, clickstack_table, or clickstack_search with sourceId "${sourceId}" and the metric tables above.`, }, }, null, @@ -291,7 +291,7 @@ async function describeSourceSchema( const lcValuesHint = skippedStages.length > 0 ? 'Value sampling was partially skipped due to timeout. ' + - 'The values shown may be incomplete — verify with a hyperdx_search query if needed.' + 'The values shown may be incomplete — verify with a clickstack_search query if needed.' : 'These are the REAL values in your data — use them in filters instead of guessing. ' + 'Example: where: "SeverityText:error" (if \'error\' appears in the sampled values above).'; @@ -305,7 +305,7 @@ async function describeSourceSchema( lowCardinalityValues: lcValuesHint, }, nextSteps: { - query: `Use hyperdx_timeseries, hyperdx_table, or hyperdx_search with sourceId "${sourceId}" and the columns/attributes above.`, + query: `Use clickstack_timeseries, clickstack_table, or clickstack_search with sourceId "${sourceId}" and the columns/attributes above.`, mapAttributeAccess: "Use bracket syntax for map columns: ResourceAttributes['service.name'], SpanAttributes['http.method']", }, @@ -335,14 +335,14 @@ export function registerDescribeSource( const { teamId } = context; server.registerTool( - 'hyperdx_describe_source', + 'clickstack_describe_source', { title: 'Describe Source Schema', description: 'CALL THIS BEFORE WRITING QUERIES — prevents unknown-column errors.\n\n' + 'Returns the full column schema, map-attribute keys, and sampled low-cardinality ' + 'values (e.g. SeverityText, StatusCode, ServiceName) for a single data source.\n\n' + - 'Workflow: call hyperdx_list_sources first to get source IDs, then call this tool ' + + 'Workflow: call clickstack_list_sources first to get source IDs, then call this tool ' + 'for each source you plan to query.\n\n' + 'Returns:\n' + '- columns[]: column name, ClickHouse type, and JS type\n' + @@ -356,12 +356,12 @@ export function registerDescribeSource( sourceId: z .string() .describe( - 'The source ID to describe. Get this from hyperdx_list_sources.', + 'The source ID to describe. Get this from clickstack_list_sources.', ), }), }, withToolTracing( - 'hyperdx_describe_source', + 'clickstack_describe_source', context, async ({ sourceId }) => { const controller = new AbortController(); @@ -388,7 +388,7 @@ export function registerDescribeSource( if (e instanceof Error && e.message === 'DESCRIBE_TIMEOUT') { logger.warn( { teamId, sourceId }, - 'hyperdx_describe_source timed out', + 'clickstack_describe_source timed out', ); return { isError: true, @@ -397,7 +397,7 @@ export function registerDescribeSource( type: 'text' as const, text: 'Schema discovery timed out. The ClickHouse server may be under load. ' + - 'Try again, or use hyperdx_list_sources for basic source info without schema details.', + 'Try again, or use clickstack_list_sources for basic source info without schema details.', }, ], }; diff --git a/packages/api/src/mcp/tools/sources/listSources.ts b/packages/api/src/mcp/tools/sources/listSources.ts index 7efe719d06..0c1e614689 100644 --- a/packages/api/src/mcp/tools/sources/listSources.ts +++ b/packages/api/src/mcp/tools/sources/listSources.ts @@ -15,21 +15,21 @@ export function registerListSources( const { teamId } = context; server.registerTool( - 'hyperdx_list_sources', + 'clickstack_list_sources', { title: 'List Sources & Connections', description: 'List all data sources (logs, metrics, traces) and database connections available to this team. ' + 'Returns source IDs, names, kinds, and connection IDs as a lightweight catalog.\n\n' + - 'NEXT STEP: After identifying the source(s) you need, call hyperdx_describe_source with the ' + + 'NEXT STEP: After identifying the source(s) you need, call clickstack_describe_source with the ' + 'sourceId to get the full column schema, attribute keys, and sampled values. ' + 'This two-step approach avoids fetching expensive schema details for sources you do not need.\n\n' + - 'NOTE: For most queries, use source IDs with hyperdx_timeseries, hyperdx_table, ' + - 'hyperdx_search, or hyperdx_event_patterns. ' + - 'Connection IDs are only needed for hyperdx_sql (raw ClickHouse SQL).', + 'NOTE: For most queries, use source IDs with clickstack_timeseries, clickstack_table, ' + + 'clickstack_search, or clickstack_event_patterns. ' + + 'Connection IDs are only needed for clickstack_sql (raw ClickHouse SQL).', inputSchema: z.object({}), }, - withToolTracing('hyperdx_list_sources', context, async () => { + withToolTracing('clickstack_list_sources', context, async () => { const [sources, connections] = await Promise.all([ getSources(teamId.toString()), getConnectionsByTeam(teamId.toString()), @@ -85,9 +85,9 @@ export function registerListSources( name: c.name, })), nextStep: - 'Call hyperdx_describe_source with a sourceId above to get the full column schema, ' + + 'Call clickstack_describe_source with a sourceId above to get the full column schema, ' + 'attribute keys, and sampled low-cardinality values before writing queries. ' + - 'connectionId is only needed for hyperdx_sql.', + 'connectionId is only needed for clickstack_sql.', }; return { content: [ diff --git a/packages/api/src/mcp/tools/trace/breakdown.ts b/packages/api/src/mcp/tools/trace/breakdown.ts index 7d4887ff01..0fd1a2f0d2 100644 --- a/packages/api/src/mcp/tools/trace/breakdown.ts +++ b/packages/api/src/mcp/tools/trace/breakdown.ts @@ -1,7 +1,7 @@ /** - * hyperdx_trace_top_time_consuming_operations + * clickstack_trace_top_time_consuming_operations * - * Aggregate counterpart to hyperdx_trace_waterfall. Given a parent-span + * Aggregate counterpart to clickstack_trace_waterfall. Given a parent-span * filter (a service + operation, optionally constrained to slow durations), * return the child operations consuming the most cumulative time across all * traces matching the parent filter — ranked by total_time_ms DESC. @@ -34,7 +34,7 @@ const traceBreakdownSchema = z.object({ .string() .describe( 'Trace source ID. Must be a source of kind="trace". ' + - 'Call hyperdx_list_sources to find available sources.', + 'Call clickstack_list_sources to find available sources.', ), parentFilter: z .string() @@ -102,7 +102,7 @@ export function registerTraceBreakdown(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', { title: 'Top Time-Consuming Operations Across Matching Traces', description: @@ -138,7 +138,7 @@ export function registerTraceBreakdown(server: McpServer, context: McpContext) { 'contained at least one such span), `p50_ms`, `p99_ms`. Plus a ' + '`summary` block with the matched-parent count.\n\n' + 'NEXT STEP after this tool: once a dominant slow child operation is ' + - 'identified, the canonical follow-up is hyperdx_event_deltas with ' + + 'identified, the canonical follow-up is clickstack_event_deltas with ' + 'slow-vs-fast spans of THAT child operation as target/baseline ' + "(target = {where: SpanName='' AND Duration > X}, " + "baseline = {where: SpanName='' AND Duration <= Y}). " + @@ -147,13 +147,13 @@ export function registerTraceBreakdown(server: McpServer, context: McpContext) { 'CROSS-SERVICE BREAKDOWN: this tool does NOT scope children to the ' + "parent's service. Slow cross-service calls (database, cache, " + 'upstream HTTP) surface naturally — useful for triage.\n\n' + - 'PAIR TOOL: hyperdx_trace_waterfall returns ONE concrete trace as a ' + + 'PAIR TOOL: clickstack_trace_waterfall returns ONE concrete trace as a ' + "parent/child tree. Use it for an example after this tool's " + 'aggregate breakdown has pointed you at the slow downstream operation.', inputSchema: traceBreakdownSchema, }, withToolTracing( - 'hyperdx_trace_top_time_consuming_operations', + 'clickstack_trace_top_time_consuming_operations', context, async (rawInput: TraceBreakdownInput) => { const input = traceBreakdownSchema.parse(rawInput); @@ -174,7 +174,7 @@ export function registerTraceBreakdown(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source not found: ${input.sourceId}. Call hyperdx_list_sources to find available source IDs.`, + text: `Source not found: ${input.sourceId}. Call clickstack_list_sources to find available source IDs.`, }, ], }; @@ -185,7 +185,7 @@ export function registerTraceBreakdown(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source ${input.sourceId} is kind="${source.kind}". hyperdx_trace_top_time_consuming_operations requires a source of kind="trace".`, + text: `Source ${input.sourceId} is kind="${source.kind}". clickstack_trace_top_time_consuming_operations requires a source of kind="trace".`, }, ], }; diff --git a/packages/api/src/mcp/tools/trace/waterfall.ts b/packages/api/src/mcp/tools/trace/waterfall.ts index b37354cac6..ba7293e2bc 100644 --- a/packages/api/src/mcp/tools/trace/waterfall.ts +++ b/packages/api/src/mcp/tools/trace/waterfall.ts @@ -23,7 +23,7 @@ const traceSchema = z.object({ .string() .describe( 'Trace source ID. Must be a source of kind "trace". ' + - 'Call hyperdx_list_sources to find available sources.', + 'Call clickstack_list_sources to find available sources.', ), traceId: z .string() @@ -169,7 +169,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { const { teamId } = context; server.registerTool( - 'hyperdx_trace_waterfall', + 'clickstack_trace_waterfall', { title: 'Trace Waterfall (single trace)', description: @@ -179,7 +179,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { 'the cascade for you instead of forcing the model to write self-JOINs in raw SQL.\n\n' + 'NOT THE RIGHT TOOL when the question is "where does the time go across MANY ' + 'slow traces of operation X" — that is the aggregate question, and the answer ' + - 'is hyperdx_trace_top_time_consuming_operations (called per affected ' + + 'is clickstack_trace_top_time_consuming_operations (called per affected ' + '(service, operation) with `minParentDurationMs`). Use this tool only when you ' + 'want a single concrete example to inspect, or after the aggregate breakdown ' + 'has already identified a suspicious operation.\n\n' + @@ -199,7 +199,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { "Prefer this over running raw SQL with JOINs on TraceId — it uses the source's " + 'configured traceIdExpression / parentSpanIdExpression / spanIdExpression so ' + 'attribute extraction stays consistent with the rest of the platform.\n\n' + - 'PAIR TOOL: hyperdx_trace_top_time_consuming_operations is the aggregate ' + + 'PAIR TOOL: clickstack_trace_top_time_consuming_operations is the aggregate ' + 'counterpart — given a parent-span filter, it ranks child operations by total ' + 'time across MANY matching traces. Use that when the question is "where does ' + 'time go in slow X" (aggregate), and use THIS tool when the question is "show ' + @@ -207,7 +207,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { inputSchema: traceSchema, }, withToolTracing( - 'hyperdx_trace_waterfall', + 'clickstack_trace_waterfall', context, async (rawInput: TraceInput) => { const input = traceSchema.parse(rawInput); @@ -227,7 +227,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source not found: ${input.sourceId}. Call hyperdx_list_sources to find available source IDs.`, + text: `Source not found: ${input.sourceId}. Call clickstack_list_sources to find available source IDs.`, }, ], }; @@ -238,7 +238,7 @@ export function registerTraceWaterfall(server: McpServer, context: McpContext) { content: [ { type: 'text' as const, - text: `Source ${input.sourceId} is kind="${source.kind}". hyperdx_trace_waterfall requires a source of kind="trace".`, + text: `Source ${input.sourceId} is kind="${source.kind}". clickstack_trace_waterfall requires a source of kind="trace".`, }, ], }; diff --git a/packages/api/src/mcp/utils/tracing.ts b/packages/api/src/mcp/utils/tracing.ts index 38809bcd81..37f12870ec 100644 --- a/packages/api/src/mcp/utils/tracing.ts +++ b/packages/api/src/mcp/utils/tracing.ts @@ -5,7 +5,7 @@ import logger from '@/utils/logger'; import type { McpContext } from '../tools/types'; -const mcpTracer = opentelemetry.trace.getTracer('hyperdx-mcp', CODE_VERSION); +const mcpTracer = opentelemetry.trace.getTracer('clickstack-mcp', CODE_VERSION); type ToolResult = { content: { type: 'text'; text: string }[]; diff --git a/packages/common-utils/src/core/eventDeltas.ts b/packages/common-utils/src/core/eventDeltas.ts index 060b40d3bc..e969e4be57 100644 --- a/packages/common-utils/src/core/eventDeltas.ts +++ b/packages/common-utils/src/core/eventDeltas.ts @@ -1,6 +1,6 @@ /** * Event-deltas algorithm — pure functions used by both the HyperDX UI - * (DBDeltaChart) and the MCP server (hyperdx_event_deltas tool). + * (DBDeltaChart) and the MCP server (clickstack_event_deltas tool). * * Given two row sets — a target group and a baseline group — the algorithm * ranks each property by how differently its values are distributed between @@ -22,7 +22,7 @@ */ export function flattenData(data: Record): Record { const result: Record = {}; - // eslint-disable-next-line security/detect-object-injection -- prop is built from known object keys via recursion, not user input + function recurse(cur: Record, prop: string) { if (Object(cur) !== cur) { result[prop] = cur; // eslint-disable-line security/detect-object-injection