MCP Quick Start (for AI agents)
This page is for the consumer side — you're pointing an MCP-aware client (Claude Desktop, MCP Inspector, a custom LLM agent, an MCP-aware ops bot) at a running CritterWatch BFF and want to discover + invoke its tools.
For the server side (mount path, tool catalog, RBAC enforcement, licensing, composition with per-store MCP servers), see MCP integration.
1. Confirm the server is mounted
The BFF mounts MCP at /api/mcp over streamable HTTP. Confirm with a POST (an empty body is enough for the transport handshake):
curl -i -X POST http://localhost:5173/api/mcpA 406 Not Acceptable or 400 Bad Request is the correct response to a hand-rolled POST without MCP framing — it confirms the route exists and a real MCP client should connect.
If you instead get a 404, the MCP server isn't mounted. Verify app.MapCritterWatchMcp() runs in the host's Program.cs.
2. Connect a client
Option A — MCP Inspector (no code, fastest path to confirm)
The official @modelcontextprotocol/inspector gives you a browser UI for browsing tools and invoking them:
npx @modelcontextprotocol/inspectorIn the Inspector UI:
- Set Transport to Streamable HTTP.
- Set URL to
http://localhost:5173/api/mcp(or wherever your BFF runs). - If the host enforces auth, add your bearer token / cookie under Headers.
- Click Connect.
The tool catalog populates the left-hand panel.
Option B — Claude Desktop
Configure the MCP server in Claude Desktop's config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS; equivalent on Windows / Linux). The transport is streamable HTTP, not stdio:
{
"mcpServers": {
"critterwatch": {
"url": "http://localhost:5173/api/mcp",
"transport": "streamableHttp",
"headers": {
"Authorization": "Bearer <your token>"
}
}
}
}Restart Claude Desktop. The CritterWatch tools become available to the model with no further prompting.
Option C — Custom client (.NET / Python / TypeScript)
Any MCP SDK that speaks streamable HTTP works. The minimum is:
- POST to
/api/mcpwithAccept: application/json, text/event-stream. - Carry the
Authorizationheader (or cookie) the host's auth scheme expects. - Follow the standard MCP framing:
initialize→tools/list→tools/call.
3. Discover the tool catalog
After connecting, the MCP tools/list call returns the live catalog. On a stock CritterWatch BFF you'll see 33 tools:
- 12 read tools —
list_active_alerts,summarize_cluster_health,get_backlog_state,query_recent_traces, etc. License-gated; no RBAC. - 21 action tools —
replay_dead_letters,rebuild_projection,pause_listener,add_tenant, etc. License + RBAC-gated.
See the full catalog for the exact list and capability mappings.
4. Invoke a read tool
Read tools are the safest place to start — they're idempotent, RBAC-free (license-gated only), and return immediately useful diagnostic data.
list_active_alerts example via Inspector:
Tool: list_active_alerts
Arguments: {}Returns:
{
"content": [
{
"type": "text",
"text": "{ \"alerts\": [ { \"id\": \"…\", \"severity\": \"Critical\", \"serviceName\": \"trip-service\", \"title\": \"DLQ depth > 100\", \"raisedAt\": \"2026-06-04T14:23:00Z\" } ] }"
}
]
}(The MCP convention is to return JSON-as-text in the content[].text field; the agent parses it back.)
If the host doesn't have a license, you'll see a LicenseMissing envelope instead — same shape as the deny envelope below, with error: "LicenseMissing".
5. Invoke an action tool (with RBAC)
Action tools require both license + RBAC. Try rebuild_projection:
Tool: rebuild_projection
Arguments: { "serviceName": "trip-service", "agentUri": "marten://projection/TripSummary:All" }On allow:
{
"content": [
{
"type": "text",
"text": "{ \"status\": \"Accepted\", \"capability\": \"projection.rebuild\" }"
}
]
}The command is published; the projection rebuild runs in the monitored service. The MCP call returns immediately on accept — completion comes via the normal telemetry channel (the Projections page in the SPA, or a follow-up get_projection_lag poll from the agent).
6. The deny envelope
When the principal isn't authorized for the capability:
{
"content": [
{
"type": "text",
"text": "{ \"error\": \"Forbidden\", \"message\": \"Caller is not authorized for capability 'projection.rebuild' on resource 'trip-service'.\", \"capability\": \"projection.rebuild\", \"resource\": \"trip-service\" }"
}
]
}The agent should surface this back to the user verbatim — the capability field is the exact grant the user is missing. Don't retry on deny; the agent can't escalate its own privileges.
When the host has no license:
{ "error": "LicenseMissing", "message": "…" }(No capability field — the license check runs ahead of RBAC.)
7. Tenant-scoped invocations
The three projection action tools (pause_projection, restart_projection, rebuild_projection) accept an optional tenantId. When supplied, the action runs against only that tenant's projection shard, and the RBAC resource scope shifts to {serviceName}:{tenantId}:
Tool: rebuild_projection
Arguments: {
"serviceName": "trip-service",
"agentUri": "marten://projection/TripSummary:All",
"tenantId": "acme-corp"
}The deny envelope on this scope:
{
"error": "Forbidden",
"message": "Caller is not authorized for capability 'projection.rebuild' on resource 'trip-service:acme-corp'.",
"capability": "projection.rebuild",
"resource": "trip-service:acme-corp"
}This is the same {serviceName}:{tenantId} convention the SPA's per-tenant Rebuild button uses and the HTTP API's per-tenant calls use — your authorizer sees one scope shape across all three surfaces. See RBAC Recipes → Per-tenant scoping for the authorizer-side implementation.
8. When read tools come back empty
A read tool that returns an empty list is usually one of:
- Nothing matches.
list_active_alertsreturns{ "alerts": [] }when no alerts are firing. - License missing. Read tools are license-gated; an unlicensed host returns the
LicenseMissingenvelope, not an empty result. - Per-service binding misconfigured. Trace tools (
query_recent_traces,get_trace) depend on the service having aITraceProviderbinding in CritterWatch's Trace Providers settings. An unbound service returns an empty trace list with a hint in the response — bind a provider in the SPA's Settings → Trace Providers.
See also
- MCP integration — server-side reference (mount path, full tool catalog, RBAC enforcement, license, composition).
- RBAC — the capability ↔ tool mapping, deny envelope shape, off-mode semantics.
- RBAC Recipes — worked examples for wiring
ICritterWatchAuthorizer, including the per-tenant scoping shape MCP action tools use.
