Skip to content

Add an MCP server that mimics the dashboard#389

Open
jbrooksuk wants to merge 5 commits into
mainfrom
claude/cachet-mcp-server-3fi8l8
Open

Add an MCP server that mimics the dashboard#389
jbrooksuk wants to merge 5 commits into
mainfrom
claude/cachet-mcp-server-3fi8l8

Conversation

@jbrooksuk

@jbrooksuk jbrooksuk commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

Adds a first-party MCP server built on laravel/mcp, so AI agents can operate a Cachet status page over the Model Context Protocol with the same capabilities the dashboard exposes — gated by admin settings and authenticated with the existing API token feature.

What changed

Endpoint & settings

  • Streamable-HTTP MCP endpoint at {cachet.path}/mcp (default /status/mcp), registered alongside the API routes with its own cachet:mcp middleware group (cachet.mcp_middleware) and throttle:cachet-mcp limiter (CACHET_MCP_RATE_LIMIT, default 300/min).
  • Two new admin settings mirroring the API pair, editable from Manage Cachet: Enable MCP server (mcp_enabled, default off → all MCP requests 404) and Require authentication (mcp_protected, default on → 401 without a Sanctum token). New EnsureMcpIsEnabled / AuthenticateMcpIfProtected middleware mirror their API counterparts.
  • The demo seeder enables MCP with authentication off, so demo instances expose the public read tools out of the box.

Tools (41, one per operation)

  • Cover the ten API-token resources: components, component groups, incidents, incident updates, incident templates, schedules, schedule updates, metrics, metric points, and subscribers — including the dashboard's special verbs (record_incident_update auto-resolves on fixed, record_schedule_update completes via completed_at, add_metric_point, subscriber verification on create).
  • Write tools call the same Cachet\Actions\** classes and request Data objects as the dashboard and REST API, so validation, webhooks, and subscriber notifications fire identically.
  • Auth mirrors the REST API: write tools require a token with the matching existing ability (incidents.manage, components.delete, …), enforced via shouldRegister() — which gates both tools/list and tools/call — plus an in-handle check. Dashboard-issued API keys work unchanged. Subscriber reads stay ability-guarded (PII).
  • Read tools apply the GHSA-6ghm-wf22-pvx5 visibility scoping from Scope public API endpoints to caller visibility (GHSA-6ghm-wf22-pvx5) #367: incidents/metrics/component groups are scoped with visible(), components inherit their group's visibility (ungrouped stay public), and disabled components are hidden from guests by default.

Composer

  • laravel/mcp moves from require-dev to require at ^0.8; the Laravel 11 floor rises to ^11.45.3 across the illuminate dependencies (laravel/mcp's own minimum).

Verification

  • Full suite green (840 tests), PHPStan and Pint clean.
  • New coverage: HTTP-level gating (404/401/initialize/tools/list visibility per token ability, tools/call blocked for unregistered tools), per-resource tool tests, and visibility regression tests mirroring Scope public API endpoints to caller visibility (GHSA-6ghm-wf22-pvx5) #367's.
  • Live smoke test over the streamable HTTP transport: create/resolve incidents with a scoped token, ability denial, guest read access with demo defaults, and no leakage of hidden/authenticated-only records to guests.

🤖 Generated with Claude Code

https://claude.ai/code/session_0157S7dDNDwYuqWsqeg1DB94

claude added 2 commits July 14, 2026 07:47
Introduces a first-party MCP server built on laravel/mcp, exposed as a
streamable HTTP endpoint at {cachet.path}/mcp and gated by two new admin
settings that mirror the existing API settings pair:

- "Enable MCP server" (mcp_enabled, default off) returns 404 for all MCP
  requests when disabled.
- "Require authentication" (mcp_protected, default on) requires a Sanctum
  API token for every MCP connection. When off, read-only tools are
  public while write tools still require a token with the matching
  ability, mirroring the REST API's semantics.

The server registers 41 tools covering the ten API token resources:
components, component groups, incidents, incident updates, incident
templates, schedules, schedule updates, metrics, metric points, and
subscribers. Write tools call the same Action classes and request data
objects as the dashboard and REST API, so validation, webhooks, and
subscriber notifications fire identically. Tools are hidden from both
tools/list and tools/call unless the session's token holds the matching
ability, using the existing ability names so dashboard-issued API keys
work unchanged. Subscriber reads stay ability-guarded since they expose
PII.

laravel/mcp moves from require-dev to require at ^0.8, which raises the
Laravel 11 floor to ^11.45.3 across the illuminate dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157S7dDNDwYuqWsqeg1DB94
Applies the GHSA-6ghm-wf22-pvx5 fix (#367) to the MCP server, which was
written against the pre-fix pattern: read tools now scope incidents,
metrics, and component groups with the HasVisibility scope, and
components inherit their group's visibility, mirroring
ComponentController. Disabled components are hidden by default and only
reachable by authenticated callers via the enabled argument; get tools
return not-found errors for out-of-scope records. Guests also no longer
see disabled components inside component groups. The caller check reuses
the ChecksApiAuthentication concern introduced by the fix.

The demo seeder now enables the MCP server with authentication not
required, so demo instances expose the public read tools while write
tools continue to require API token abilities.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157S7dDNDwYuqWsqeg1DB94
@jbrooksuk
jbrooksuk force-pushed the claude/cachet-mcp-server-3fi8l8 branch from 9981c78 to f0a55ee Compare July 14, 2026 07:49
@jbrooksuk jbrooksuk changed the title Implement FilamentUser on the User model Add an MCP server that mimics the dashboard Jul 14, 2026
jbrooksuk and others added 3 commits July 14, 2026 09:55
On the dependency floor, spatie/laravel-data skips a data object's
validation rules for absent properties that declare default values, so
create_incident calls with neither message nor template passed
validation and failed on the database's NOT NULL constraint instead of
returning a validation error. Enforce the required_without pair at the
tool boundary so the behaviour is identical across all supported
laravel-data versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157S7dDNDwYuqWsqeg1DB94
Linking components to an incident writes the incident_components pivot,
not the component's own status column - the status page overlays the
pivot via Component::latest_status while the incident is unresolved.
That is invisible over MCP, where the component presenter only returned
the raw status column, so an agent that created an incident and re-read
the component concluded the status write had failed.

Component payloads now include latest_status alongside status, the
create_incident schema explains that the component status is a display
overlay rather than a mutation, and the server instructions describe the
base-versus-displayed status model. New tests assert the overlay is
visible through get_component after create_incident and reverts when a
fixed update is recorded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157S7dDNDwYuqWsqeg1DB94
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.

2 participants