feat(mcp): add alert, saved search, and webhook MCP tools#2274
Conversation
🦋 Changeset detectedLatest commit: 71cea47 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
E2E Test Results✅ All tests passed • 176 passed • 3 skipped • 1261s
Tests ran across 4 shards in parallel. |
PR ReviewWell-structured feature addition with comprehensive integration tests and proper team-scoping throughout. Multi-tenancy is correctly enforced: Non-critical items worth a look:
No blocking issues; the feature itself is clean and well-tested. |
Deep Review🟡 P2 -- recommended
🔵 P3 nitpicks (3)
Reviewers (5): correctness, testing, maintainability, project-standards, api-contract. Testing gaps:
|
Add five new MCP tools for managing alerts, saved searches, and webhooks: - hyperdx_get_alert: list/detail alerts with state filtering and history - hyperdx_save_alert: create/update alerts with threshold, channel, and schedule config - hyperdx_get_webhook: list webhook destinations for alert channels - hyperdx_get_saved_search: list/detail saved searches - hyperdx_save_saved_search: create/update saved searches with filters and tags Also makes McpContext.userId required (rejects requests without it), enhances getLoggedInAgent fixture for multi-tenancy test isolation, and adds comprehensive integration tests for all new tools.
…plicit BaseError instanceof check - saveSavedSearch: remove ...existing.toJSON() spread that passed a populated createdBy object where Mongoose expects an ObjectId. savedSearchData already contains every user-editable field. - saveAlert: replace stringly-typed e.name heuristic with explicit e instanceof BaseError check for error message extraction.
…e, slim list query, simplify channel schema, remove non-null assertions - Add translateSavedSearchDocumentToExternalSavedSearch translator so saved search responses use 'id' (not '_id'), string IDs, and no __v, consistent with alert/dashboard tools. - Replace getSavedSearches controller call with a slim SavedSearch.find(team, 'name tags').lean() for the list endpoint, avoiding unnecessary Alert.find and populate calls. - Make webhookId required in the Zod schema (was optional with runtime check), removing the dead-branch channel validation and the non-null assertion in toAlertChannel. - Replace input.id! non-null assertions with early-return narrowing via a local alertId variable. - Update saved search tests to expect the new external response shape.
32f4141 to
beb9ec4
Compare
| "lint:fix": "npx eslint . --ext .ts --fix", | ||
| "ci:lint": "yarn lint && yarn tsc --noEmit && yarn lint:openapi", | ||
| "ci:int": "DOTENV_CONFIG_PATH=.env.test DOTENV_CONFIG_OVERRIDE=true jest --runInBand --ci --forceExit --coverage", | ||
| "ci:int": "DOTENV_CONFIG_PATH=.env.test DOTENV_CONFIG_OVERRIDE=true jest --runInBand --ci --forceExit", |
There was a problem hiding this comment.
backporting coverage removal to OSS as this PR seems to have crossed the memory threshold and now the int tests are failing
… queryParser errors to warnings - Replace getLoggedInAgent with second credentials in alert team-scoping tests with a fabricated McpContext using a non-existent teamId. The single-tenant registration route returns 409 when a team already exists, making it impossible to register a second user mid-test. - Use client?.close() in all MCP test afterEach hooks to prevent cascade failures when beforeEach fails. - Downgrade console.error to console.warn in queryParser for non-fatal buildKvItemsLookup and enable_full_text_index fallbacks.
| @mkdir -p $(HDX_CI_LOGS_DIR) | ||
| docker compose -p $(HDX_CI_PROJECT) -f ./docker-compose.ci.yml up -d --quiet-pull | ||
| bash -c 'set -o pipefail; npx nx run-many -t ci:int --parallel=false 2>&1 | tee $(HDX_CI_LOGS_DIR)/ci-int.log'; ret=$$?; \ | ||
| bash -c 'set -o pipefail; npx nx run-many -t ci:int --parallel=false --output-style=stream 2>&1 | tee $(HDX_CI_LOGS_DIR)/ci-int.log'; ret=$$?; \ |
There was a problem hiding this comment.
Backporting change to improve logging of int tests - failures previously may not be shown
| // Pre-fetch KV items lookup (map column -> KV items column with text(tokenizer=array) index) | ||
| this.kvItemsLookupPromise = this.buildKvItemsLookup().catch(error => { | ||
| console.error('Error building KV items lookup:', error); | ||
| console.warn('Error building KV items lookup:', error); |
There was a problem hiding this comment.
This was very chirpy in int tests - moved to warn since it does not break (warns are suppressed in int tests)
| updatedAt?: string; | ||
| }; | ||
|
|
||
| export function translateSavedSearchDocumentToExternalSavedSearch(doc: { |
There was a problem hiding this comment.
I know we’re using these translateXXX methods in the external API, but it feels error-prone and not very straightforward to use. What do you think about moving this logic into the model file (as a method)?
There was a problem hiding this comment.
Makes sense - let me ship this in a fast-follow :)
Summary
hyperdx_get_alert,hyperdx_save_alert,hyperdx_get_webhook,hyperdx_get_saved_search, andhyperdx_save_saved_search— enabling AI agents to manage alerts, saved searches, and webhook destinations via the MCP server.McpContext.userIdrequired and rejects MCP requests without a user ID (403), tightening the auth surface.getLoggedInAgenttest fixture to accept custom credentials for multi-tenancy isolation tests.Details
New Alert Tools (
tools/alerts/)hyperdx_get_alert: List all alerts as a slim summary (id, name, state, source, interval) with optional state filtering, or get full detail with recent evaluation history for a specific alert.hyperdx_save_alert: Create or update alerts with full support for threshold types (including range-basedbetween/not_between), webhook channels, schedule configuration, and both saved-search and dashboard-tile sources. Includes runtime cross-field validation to work around MCP SDK limitations withZodEffects.hyperdx_get_webhook: List webhook destinations (id, name, service) without exposing sensitive fields (url, headers, body).New Saved Search Tools (
tools/savedSearches/)hyperdx_get_saved_search: List saved searches (id, name, tags) or get full detail including query, source, filters.hyperdx_save_saved_search: Create or update saved searches with support for Lucene/SQL where clauses, structured filters (includingsql_ast), column selection, ordering, and tags.Auth Tightening
McpContext.userIdis now required (stringinstead ofstring | undefined).mcp.user.idattribute.Test Coverage
userIdin context.Documentation
MCP.mdtool table with the 5 new tools.