Skip to content

Push the status dashboard's verdict to a Statuspage component - #151

Merged
aterga merged 13 commits into
mainfrom
claude/statuspage-pusher
Aug 26, 2026
Merged

aterga merged 13 commits into
mainfrom
claude/statuspage-pusher

Conversation

@aterga

@aterga aterga commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Puts MCP health on status.internetcomputer.org. That page is a hosted Atlassian Statuspage — its components live in the Statuspage service, not in any repository — so the way to surface MCP status there is to push it. This PR adds an optional, off-by-default pusher to the status dashboard (monitoring/mcp-status) that maps each probe run's overall verdict onto one Statuspage component and PATCHes it through the Statuspage REST API.

Context: the human-facing MCP status dashboard is mirrored at internetcomputer.org/icp-mcp/status/, but its live-data API is not reachable from that origin yet, and mcp.internetcomputer.org is being handed over to the IC gateway. Publishing the verdict to the official status page gives MCP a monitoring surface that is independent of both migrations (per review discussion on dfinity/internetcomputer-org#71).

Mapping (deliberately coarse — the public status page is not the detailed dashboard):

Dashboard verdict Component status
pass operational
warn degraded_performance
fail, some MCP endpoint checks still passing partial_outage
fail with every MCP endpoint check failing major_outage

Related issues

Follow-up to the /mcp → /icp-mcp landing-page work in dfinity/internetcomputer-org#71 and #72 (status dashboard's data API not reachable from the mirror's origin).

Changes

  • monitoring/mcp-status/statuspage.js — the pusher: verdict mapping, env-based config (STATUSPAGE_PAGE_ID, STATUSPAGE_API_KEY, STATUSPAGE_COMPONENT_ID, optional STATUSPAGE_PUSH_INTERVAL_MS floored at 15 s), change-driven PATches (plus one at startup, since a restarted process cannot know the remote state), overlapping ticks join the in-flight run, failures logged (API key redacted, no response body) and retried next interval, timer unref'd.
  • monitoring/mcp-status/server.js — starts the pusher only when all three STATUSPAGE_* variables are set; a partial configuration logs a warning naming the missing variables instead of silently not pushing. Reuses the /api/status probe cache, so it adds at most one probe run per interval.
  • monitoring/mcp-status/statuspage.test.js — unit tests (stubbed fetch, no network): mapping, config resolution/flooring, PATCH request shape (Authorization: OAuth …), change-only pushes, retry-after-failed-push, probe-failure survival, no key leakage in errors.
  • deploy/native/imcp-status.service — loads an optional /etc/imcp-status/statuspage.env (EnvironmentFile=-…, created by the operator, mode 600), keeping the API key out of the repo and the rendered unit.
  • monitoring/mcp-status/README.md — setup and mapping documentation.

One-time ops step (not in this PR): in the Statuspage admin, create the component (e.g. "ICP MCP server"), mint an API key, and place the three variables in /etc/imcp-status/statuspage.env on the deployed host, then systemctl restart imcp-status.

Testing

  • cargo build --locked --all-targets
  • cargo test --locked --all-targets
  • cargo fmt --all / cargo clippy --all-targets (tree is warning-free)
  • npm test --prefix monitoring/mcp-status (if the dashboard changed) — 40/40 pass (26 existing + 14 new)

No Rust code is touched, so the cargo items are unaffected (CI runs them regardless).

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments) updated for any user-visible change.
  • No secrets, credentials, or internal-only information are included.

Generated by Claude Code

status.internetcomputer.org is a hosted Atlassian Statuspage: its
components live in the Statuspage service, not in a repository, so the
way to surface MCP health there is to push it. Add an optional pusher to
the status dashboard that maps each probe run's overall verdict onto one
Statuspage component and PATCHes it through the Statuspage REST API.

- monitoring/mcp-status/statuspage.js: the pusher. Mapping: pass ->
  operational, warn -> degraded_performance, fail -> partial_outage,
  or major_outage when every MCP endpoint check failed (server down or
  unreachable). Pushes are change-driven (plus once at startup, since a
  restarted process cannot know the remote state), the interval is
  floored at 15 s, overlapping ticks join the in-flight run, push
  failures are logged (API key redacted) and retried, and the timer is
  unref'd so the pusher never keeps the process alive.
- monitoring/mcp-status/server.js: start the pusher when the three
  STATUSPAGE_* variables are set; off by default, so existing
  deployments are unaffected. It reuses the /api/status probe cache, so
  it adds at most one probe run per interval.
- monitoring/mcp-status/statuspage.test.js: unit tests (stubbed fetch,
  no network) for the mapping, config resolution, the PATCH request
  shape, change-only pushes, and retry-after-failure.
- deploy/native/imcp-status.service: load an optional
  /etc/imcp-status/statuspage.env (created by the operator, mode 600)
  so the API key stays out of the repository and the rendered unit.
- monitoring/mcp-status/README.md: setup (create the component in the
  Statuspage admin, mint an API key), variables, and the mapping table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional publishing of MCP dashboard verdicts to Atlassian Statuspage.

Changes:

  • Maps dashboard verdicts to Statuspage component states.
  • Adds periodic, change-driven Statuspage updates and configuration.
  • Documents deployment setup and adds unit tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
monitoring/mcp-status/statuspage.js Implements Statuspage publishing.
monitoring/mcp-status/statuspage.test.js Tests mapping, configuration, and retries.
monitoring/mcp-status/server.js Starts the optional pusher.
monitoring/mcp-status/README.md Documents configuration and mapping.
deploy/native/imcp-status.service Loads optional Statuspage credentials.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread monitoring/mcp-status/statuspage.js Resolved
Comment thread monitoring/mcp-status/statuspage.js Resolved
Comment thread monitoring/mcp-status/statuspage.js Resolved
Three review findings, each with a regression test:

- Cap the push interval below Node's signed 32-bit timer limit.
  setInterval coerces larger delays to 1 ms, so an uncapped oversized
  STATUSPAGE_PUSH_INTERVAL_MS would fire near-continuously - the exact
  opposite of the minimum-interval floor's intent.
- Bound the Statuspage PATCH with AbortSignal.timeout (10 s). A stalled
  request kept the tick's in-flight promise pending forever, and since
  later ticks join the in-flight run, the pusher would wedge and the
  public component would stay stale indefinitely.
- Redact the API key from error messages before truncating them. Capping
  first could cut the message mid-key, leaving a prefix that no longer
  matched the full-key replacement and so leaked into the log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
@aterga
aterga marked this pull request as ready for review August 26, 2026 11:06
@aterga
aterga requested review from a team and Copilot August 26, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread monitoring/mcp-status/statuspage.js Resolved
major_outage was unreachable in a real total outage: the every-check-
failed test ran over the whole endpoints section, but that section's
auxiliary checks deliberately do not fail when the server is
unreachable - version and metadata-consistency degrade to "warn", and
mcp-tls can stay "pass" behind a healthy reverse proxy fronting a dead
application - so the verdict was always partial_outage.

Compute the verdict from an explicit set of service-availability check
ids instead (the endpoint probes that report "fail" on a failed
request: landing page, discovery documents, the /mcp challenge, and the
OAuth endpoints). Add a realistic total-outage report test in which the
auxiliary checks are warn/pass and every availability probe fails, and
document the distinction in the README's mapping table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread monitoring/mcp-status/statuspage.js Resolved
Comment thread monitoring/mcp-status/statuspage.js Resolved
Node's fetch (Undici) keeps the underlying connection associated with an
unread response body until garbage collection. The pusher never reads
the body, and a persistent error response - exactly the case that is
retried every interval - would therefore accumulate connections over
time. Cancel the body before checking the status, on success and error
alike; its content stays out of thrown errors as before, since it is
under the remote service's control.

Regression test asserts the body is cancelled on both a 200 and an
error response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:15
Review feedback: checking res.status !== 200 was stricter than needed.
Use res.ok instead - the API returns 200 today, but a successful push
should not depend on that staying exact. Test stubs now carry ok like a
real Response, and a new test asserts a 204 is accepted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread monitoring/mcp-status/statuspage.js Resolved
Copilot AI review requested due to automatic review settings August 26, 2026 11:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread deploy/native/imcp-status.service Resolved
Comment thread monitoring/mcp-status/statuspage.js Resolved
MRmarioruci
MRmarioruci previously approved these changes Aug 26, 2026
The pusher ran the full probe suite every interval, and the suite's
Dynamic Client Registration check mints and persists a client_id on the
monitored server per run - about 1,440 junk registrations a day at the
default 60 s interval, cycling the server's 10,000-entry LRU client
store in under a week and churning its persistence.

- checks.js: checkMcpEndpoints (and runDashboard) accept mutating:false,
  which skips only the loopback DCR probe - the one check that changes
  server state. The allow-list probe stays in both modes: its
  non-allow-listed redirect is rejected before anything is stored.
- server.js: the pusher reuses a recent visitor-triggered full report
  when one exists (already paid for) and otherwise probes with
  mutating:false. The non-mutating report is not stored in the visitor
  cache, which promises the full suite.
- checks.test.js: non-mutating mode sends no loopback registration,
  omits the oauth-register check, and still runs the allow-list probe.
- README: document that the pusher's own runs are non-mutating and that
  DCR health is still verified by interactive visits and CLI runs.

The verdict mapping is unaffected: it already computes major_outage from
the availability checks present in the report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:25
Two review findings:

- Run imcp-status under a dedicated system user (created by deploy.sh)
  instead of the shared ec2-user. The dashboard process can carry a
  Statuspage API key in its environment, and same-UID processes can read
  each other's /proc/<pid>/environ, so sharing a user with the
  internet-facing imcp2 service would let a compromise of the main
  service recover the secret despite the root-only env file. The shipped
  files under /opt/imcp2 are world-readable, so no ownership changes are
  needed.
- Fix an off-by-one in the redaction regression test: with 289 prefix
  characters the 11-character key occupied indices 289-299 and survived
  slice(0, 300) whole, so the vulnerable truncate-before-redact ordering
  passed the test too. With 290 the key straddles the cap and only an
  unmatchable prefix would survive, which the test now catches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

monitoring/mcp-status/statuspage.js:145

  • This returns silently whenever all three required values are absent, even if an optional pusher variable such as STATUSPAGE_PUSH_INTERVAL_MS or STATUSPAGE_API_BASE is set. That contradicts the documented contract that only a completely unset configuration is silent and makes this partial/mistyped deployment impossible to diagnose. Include the optional variables when deciding whether any Statuspage configuration was supplied.
  if (missing.length === 3) return {};

Comment thread deploy/native/deploy.sh Resolved
Three review findings:

- Factor sanitizeForLog out of server.js into log.js and reuse it from
  statuspage.js's describeError, which carried a near-identical copy.
  describeError still redacts the API key on the full message BEFORE the
  shared sanitiser truncates it. Small log.test.js covers the sanitiser
  directly.
- Bound the pusher's report reuse by the dashboard cache TTL as well as
  the push interval. Reusing anything /api/status already considers
  expired could let a tick publish stale state, delaying an outage by up
  to two intervals.
- Join a visitor-triggered full probe run that is already in flight
  instead of starting a second concurrent suite; the full report
  satisfies the pusher, and probe load stays coalesced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

monitoring/mcp-status/README.md:148

  • This sentence is grammatically incorrect: “make … as merely partial” should be “make … appear merely partial.”
outage report as merely partial.

The dedicated imcp-status UID alone did not isolate the Statuspage API
key: the dashboard source was staged under /opt/imcp2 owned by the
deploy/SSH user, the same UID the internet-facing imcp2.service runs
as, so a compromised main service could edit server.js and have the
next dashboard restart execute its code in the secret-bearing service.

deploy.sh now installs a root-owned, non-group/other-writable copy of
monitoring/mcp-status at /opt/imcp-status on every deploy, and the unit
executes that copy. The staged tree under /opt/imcp2/monitoring remains
the shipping vehicle only. Deploy README's layout diagram updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:36
resolveStatuspageConfig stayed silent whenever the three required
variables were absent, even when an optional one (
STATUSPAGE_PUSH_INTERVAL_MS or STATUSPAGE_API_BASE) was set - so a
mistyped deployment intending to run the pusher got a silent no-op
instead of a diagnosis, contradicting the documented contract that only
a completely unset configuration is silent. Warn in that case, naming
both the optional variables that were set and the required ones that
are missing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

monitoring/mcp-status/statuspage.js:146

  • When only an optional override such as STATUSPAGE_PUSH_INTERVAL_MS or STATUSPAGE_API_BASE is set, missing.length is still 3 and this returns silently. That hides a partial configuration despite this function's documented warning behavior. Treat the pusher as entirely unset only when none of the required or optional Statuspage variables has a value.
  if (missing.length === 3) {

Comment thread deploy/native/deploy.sh Resolved
Copilot AI review requested due to automatic review settings August 26, 2026 11:39
The documented setup step - placing statuspage.env in /etc/imcp-status
(mode 600) - could not be completed on a fresh host without an
undocumented mkdir, and since the EnvironmentFile is optional, a restart
after the attempt would silently leave the pusher disabled. Create the
directory root-only (mode 700) in deploy.sh; systemd reads the
EnvironmentFile as root, so no other user needs access.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (1)

deploy/native/deploy.sh:123

  • The secret-bearing copy is still sourced from $REMOTE_DIR, which is writable by the same ec2-user as the compromised main service this isolation is meant to contain. That process can replace server.js (or race the copy after the tar upload), and cp -a will copy the malicious file—preserving symlinks as well—before root ownership is applied; the subsequent restart then runs it with access to the Statuspage key. Stream/extract the dashboard directly from the trusted deployment input into a root-owned temporary directory and atomically install it, rather than promoting files from the untrusted staging tree.
# which is also the UID the internet-facing imcp2.service runs as — a
# compromised main service could edit server.js there and have the next
# dashboard restart run its code in the secret-bearing service. Refresh the
# copy on every deploy; strip group/other write (files stay world-readable, so

Copilot AI review requested due to automatic review settings August 26, 2026 11:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

monitoring/mcp-status/checks.js:186

  • mutating: false still sends the allow-list registration request. If the guard regresses—the exact failure this probe is meant to detect—the server reaches the normal registration path and persists a new client (src/auth.rs:1913-1918). The unattended 60-second pusher would then create about 1,440 clients/day and churn the 10,000-entry LRU. Skip this probe in non-mutating runs as well, or provide a genuinely dry-run validation path; keep it in interactive/full runs.
 * @param {{ mutating?: boolean }} [opts] With `mutating: false`, skip the one
 *   check that changes server state — the loopback Dynamic Client Registration
 *   probe, which mints and persists a fresh client_id in the server's
 *   LRU-bounded registration store on every run. Interactive uses (a person
 *   loading the dashboard, a one-off CLI run) keep the default; unattended
 *   periodic callers (the Statuspage pusher) must not grind through that store.
 *   The allow-list check stays in both modes: its non-allow-listed redirect is
 *   rejected before anything is stored, so it never mutates.

Comment thread deploy/native/imcp-status.service Resolved
Non-mutating mode still sent the allow-list registration request. While
the server's hosted-redirect guard holds that stores nothing, but if the
guard regressed - the exact failure the probe detects - every run would
reach the normal registration path and mint a client, turning the
unattended pusher into a store-grinding loop precisely when the server
is broken. Non-mutating runs now skip both probes that POST to
/oauth/register; interactive dashboard visits and CLI runs still
exercise both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread monitoring/mcp-status/checks.js Resolved
Comment thread monitoring/mcp-status/server.js Resolved
runDashboard's JSDoc and the getPusherReport comment still said
non-mutating mode skips only the DCR probe; it skips both registration
probes, and the resulting report is missing two checks - which is also
why it must not enter the visitor cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWw9GUtUFEcTi75SnfnPi3
Copilot AI review requested due to automatic review settings August 26, 2026 11:54
@aterga
aterga requested a review from MRmarioruci August 26, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@aterga
aterga merged commit 5f53787 into main Aug 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants