Skip to content

feat(mcp): source & webhook CRUD tools#2702

Open
brandon-pereira wants to merge 4 commits into
mainfrom
brandon/brandon-new-mcp-tools
Open

feat(mcp): source & webhook CRUD tools#2702
brandon-pereira wants to merge 4 commits into
mainfrom
brandon/brandon-new-mcp-tools

Conversation

@brandon-pereira

@brandon-pereira brandon-pereira commented Jul 21, 2026

Copy link
Copy Markdown
Member

Why

The ClickStack MCP could read sources/webhooks but not create, update, or delete them, blocking any agent trying to automate setup (bring-your-own-shipper → ingest → build search/dashboards/alerts). This closes that gap. (Linear HDX-4740 / HDX-4742.)

What

  • New MCP tools: clickstack_save_source / clickstack_delete_source, clickstack_save_webhook / clickstack_delete_webhook. save_* omits id to create, provides id to update (matches save_alert/save_dashboard).
  • Extracted shared createWebhook / updateWebhook / deleteWebhook controllers, de-duplicating logic across the internal API, External API v2, and MCP. Preserves the v2 HTTP contract.
  • clickstack_describe_source now returns a round-trippable config block — the exact flat shape save_source accepts — so an agent can clone a source faithfully: read config, drop id, pass back to save_source.

Bug fixes

  • Generic/incidentio webhook with no body crashed alert delivery (Handlebars.compile(undefined)); now falls back to the default body template.
  • updateSource dropped the team field on findOneAndReplace, orphaning the source from team-scoped queries; fixed + regression test.

Testing

Unit (578 pass), tsc, lint all pass. Integration suites for sources, webhooks, and checkAlerts (incl. body-less-webhook regression) pass. MCP tools dogfooded end-to-end incl. a full source clone.

…e config

Adds MCP tools so the ingest -> dashboard flow can be automated end to end:
- clickstack_save_source / clickstack_delete_source
- clickstack_save_webhook / clickstack_delete_webhook
  (save creates when id is omitted, updates when provided)

Webhook create/update/delete logic is extracted into shared controllers
(createWebhook/updateWebhook/deleteWebhook): createWebhook is used by the
internal API, External API v2, and MCP; update/delete are shared by v2 + MCP
(the internal API keeps its own masked-secret flow).

clickstack_describe_source now returns a round-trippable `config` block — the
exact flat shape clickstack_save_source accepts, including fields the curated
summary previously omitted (correlation IDs, defaultTableSelectExpression,
parent/span-kind expressions, materialized views). This closes the read/write
asymmetry that made a faithful source clone impossible.

Also fixes a pre-existing latent bug: a generic/incidentio webhook persisted
without a body (body default is UI-only) crashed sendGenericWebhook on
Handlebars.compile(undefined); it now falls back to the default body template.

Also fixes updateSource dropping the team field on same-kind findOneAndReplace,
which orphaned the source from team-scoped queries.
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f9d7a91

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Minor
@hyperdx/app Minor
@hyperdx/otel-collector Minor

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

@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jul 22, 2026 3:16pm
hyperdx-storybook Ready Ready Preview, Comment Jul 22, 2026 3:16pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds MCP CRUD support for sources and webhooks. The main changes are:

  • New MCP tools to save and delete sources and webhooks.
  • Shared webhook controller helpers for create, update, and delete flows.
  • Round-trippable source config returned from clickstack_describe_source.
  • Source updates now preserve team on same-kind replacements.
  • Generic and incidentio webhooks without a body now use the default template.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
packages/api/src/controllers/webhook.ts Adds shared webhook create, update, and delete helpers with team-scoped checks.
packages/api/src/tasks/checkAlerts/template.ts Adds a default body template for generic and incidentio webhooks when no body is stored.
packages/api/src/mcp/tools/sources/saveSource.ts Adds MCP source create and update handling with connection and correlated-source validation.
packages/api/src/mcp/tools/alerts/saveWebhook.ts Adds MCP webhook create and update handling with canonical webhook validation and redacted output.
packages/api/src/controllers/sources.ts Keeps the source team field during same-kind replacement updates.

Reviews (4): Last reviewed commit: "Merge branch 'main' into brandon/brandon..." | Re-trigger Greptile

Comment thread packages/api/src/mcp/tools/sources/deleteSource.ts
Comment thread packages/api/src/controllers/webhook.ts
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 243 passed • 1 skipped • 990s

Status Count
✅ Passed 243
❌ Failed 0
⚠️ Flaky 0
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

- Reduce comments to explain non-obvious "why" only (1-2 lines each), drop
  restated-code and section-header narration.
- Webhook body fallback: only substitute the default template when body is
  null/undefined, so an explicit empty body ("") is honored (Greptile #3).
- deleteWebhook guard: match alerts on channel.webhookId alone (drop the
  channel.type filter) so a legacy/skewed alert still blocks deletion
  (Greptile #2).

Greptile #1 (dangling source references on delete) is intentionally left as-is:
it matches the existing REST DELETE /sources/:id behavior and the tool
description already warns callers to check dependents; full dependency-graph
cleanup is out of scope for this PR.
@brandon-pereira

Copy link
Copy Markdown
Member Author

Thanks @greptile-apps — addressed in 86b5688:

  • Issue 3 (empty body → default): Fixed. The fallback now only fires when webhook.body == null, so an explicit empty body ("") is sent as-is. Applied the suggested change.
  • Issue 2 (skewed alert references bypass guard): Fixed. deleteWebhook now counts referencing alerts by channel.webhookId alone (dropped the channel.type: 'webhook' predicate), so a legacy/skewed alert that still carries a webhookId continues to block deletion.
  • Issue 1 (dangling source references on delete): Intentionally left as-is. This matches the existing DELETE /api/sources/:id / /api/v2/sources/:id behavior (neither cascades or blocks on dependents), and the clickstack_delete_source tool description already warns callers that correlated sources are left dangling. Adding dependency-graph enforcement would be a broader behavior change across all delete surfaces, so it's out of scope for this PR.

Also did a comment-reduction pass across the new files (explain non-obvious "why" only, 1-2 lines).

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

All three fixes look correct after reviewing 86b5688.

Issue 3 (empty body → default): The webhook.body == null check in sendGenericWebhook correctly catches both null and undefined while preserving an explicit "" — exactly the right distinction.

Issue 2 (skewed alert references): deleteWebhook now queries { 'channel.webhookId': webhookId, team } without the channel.type predicate. The comment explaining why (channel.type was dropped) is a good addition.

Issue 1 (dangling source references): The rationale holds — matching existing REST behavior and bounding scope for this PR is the right call. The tool description warning covers the caller contract.

The comment reduction pass looks clean too; what remains explains non-obvious "why" rather than restating the code.

LGTM — no further concerns from me.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD.

Why this tier:

  • Critical-path files (2):
    • packages/api/src/routers/external-api/v2/webhooks.ts
    • packages/api/src/tasks/checkAlerts/template.ts

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 14
  • Production lines changed: 1032 (+ 895 in test files, excluded from tier calculation)
  • Branch: brandon/brandon-new-mcp-tools
  • Author: brandon-pereira

To override this classification, remove the review/tier-4 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

The query-focused eval scenarios don't exercise source/webhook mutation, so
deny the four new write tools (save/delete source, save/delete webhook)
alongside the existing dashboard/saved-search/alert write tools to keep the
agent's tool count down for faster schema loading.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Deep Review

⚠️ Degraded run — the multi-agent pipeline could not execute. Every shell command in this environment fails at the container layer (bwrap: Can't create file at /home/.mcp.json), which disabled git, gh, grep, glob, and find; dangerouslyDisableSandbox did not bypass it, the HOME-override workaround required an approval unavailable to an automated run, and WebFetch was not granted. A sub-agent hit the same wall. Consequently the diff could not be computed, and the persona reviewers plus the validator pass never ran. This review was performed manually by reading the PR-head file contents (the only working tool, Read) for the files described by the PR, then reasoning about correctness, security, API-contract, maintainability, and testing. Findings are limited to code visible in the head state; pre-existing-vs-introduced attribution and line-level diff precision are best-effort.

No critical issues found. The new code is defensively written and consistently team-scoped; the sendGenericWebhook null-body fallback and the updateSource team-injection fix both read as correct. Two lower-severity items below.

🟡 P2 -- recommended

  • packages/api/src/routers/api/webhooks.ts:386 -- the internal (UI-facing) DELETE /:id now delegates to the shared deleteWebhook, which returns a new 409 (status: 'referenced') when any alert still points at the webhook; the pre-existing internal endpoint had no such block, so a delete that used to succeed can now fail with a status the existing UI flow may not handle.
    • Fix: Confirm the internal webhook-delete UI surfaces the 409 message gracefully, and treat the new referenced-guard as an intentional contract change if so.
🔵 P3 nitpicks (1)
  • packages/api/src/mcp/tools/sources/schemas.ts:238 -- extractSourceConfig strips the Mongoose _id only from the top level of each materializedViews entry via stripMongooseId, but not from the nested aggregatedColumns[] subdocuments, so the round-trippable config emitted by clickstack_describe_source can carry stray _id fields into what is advertised as a clean save-source payload.
    • Fix: Deep-strip nested subdocument _ids (or set _id: false on the materialized-view/aggregated-column subschemas) so the emitted config contains no Mongoose-injected keys.

Reviewers (1, degraded): the persona sub-agents could not be spawned (see banner). A single manual pass covered the correctness, security, API-contract, maintainability, and testing lenses over the PR-head sources: controllers/webhook.ts, controllers/sources.ts, the internal and External-v2 webhook routers, tasks/checkAlerts/template.ts, and the MCP save/delete/describe source and webhook tools plus their shared schemas.

Testing gaps:

  • Test files were not read (scope could not be enumerated without git/grep), so the PR's stated coverage — the body-less-webhook regression test and the updateSource team-drop regression test — was not independently verified.
  • The environment blocked running the test suite, tsc, and lint, so none of the author's stated "578 pass / 0 errors" results were reproduced here.

@vercel
vercel Bot temporarily deployed to Preview – hyperdx-storybook July 22, 2026 14:11 Inactive
@brandon-pereira brandon-pereira changed the title feat(mcp): source & webhook CRUD tools with round-trippable source config feat(mcp): source & webhook CRUD tools Jul 22, 2026
@brandon-pereira
brandon-pereira requested review from a team and teeohhem and removed request for a team July 22, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant