Companion to FleetAlert AI
An MCP server with safety tiers
fleetalert-mcp exposes the live FleetAlert demo to Claude Code, Claude Desktop and any MCP client: alerts, investigation traces, usage, and the ability to start an investigation or confirm a fix. FleetAlert’s own safety tiers decide which tools exist on the wire, confirming a fix asks the human through elicitation, and the server adds the auth (bearer tokens, or OAuth for Claude Desktop) and rate limits the public demo API does not have itself.
Read the source and decision record, the FleetAlert write-up, the live demo it fronts, or the hosted endpoint's health.
Tiers, and where the human sits
A server at the default tier registers only the six read tools; the other two tiers do not exist on the wire until an operator raises it. Even at the top tier, confirm_fix cannot complete without a person answering in their own client, and the confirmation token moves server-to-server only after they do.
Connect your Claude to the demo
The server runs as a Lambda in the FleetAlert account. This token is read-only: your Claude can list the alerts, summarise an investigation and check the daily budget, and cannot start or confirm anything.
claude mcp add --transport http fleetalert \
https://xx8eypbgv2.execute-api.us-east-1.amazonaws.com/mcp \
--header "Authorization: Bearer d2067598688a6ca159dec88d3b0911805d09a2940513e869"Then ask: “List the FleetAlert alerts and summarise the trace for the one awaiting confirmation.”
Claude Desktop and claude.ai need no token: add a custom connector with the same URL and the server’s own consent page signs it in as the read-only visitor. The server is its own OAuth 2.1 authorization server, with no database behind it (ADR-0007). An investigation started from either client shows up in FleetAlert’s Activity feed tagged mcp.
The five-minute version
Tools exist by tier, or not at all
FleetAlert tags every capability read_only, proposes_action or executes_action, and its model never receives the last one. The MCP server keeps the same three names. It runs at one tier, read-only by default, and tools above that tier are not registered: a client cannot list them, so a model cannot be talked into calling them. That is FleetAlert's own standard, unreachable rather than refused, carried through the protocol.
Confirming a fix asks the human, not the model
confirm_fix is the one tool that makes something execute upstream. It takes an alert id and nothing else. Its second parameter is resolved on the server through MCP elicitation: the client puts the proposed fix, the agent's confidence and its root-cause summary in front of the person, and only an explicit approval makes the server fetch the confirmation token and call the API. The token never appears in a tool result, and a client that cannot ask is refused before any upstream call.
The server is where identity starts
The demo API has no authentication; FleetAlert bounds it with fixed scenarios and a daily cost cap. Over Streamable HTTP the server requires bearer tokens, compared by digest in constant time through the SDK's TokenVerifier protocol, so a real authorization server is a one-class swap. Each token carries a tier that can narrow what the server allows but never widen it. Over stdio there are no tokens: the process boundary is the boundary, as the spec intends.
Refusals the model can read
One server middleware wraps every tool call: who is calling, whether their tier allows the tool, and three token buckets (per caller, a tighter one for tools that spend budget, and a global one in front of the upstream API). A refusal comes back as a tool result marked as an error, with the reason and a retry time, rather than a protocol error the model never sees. The same middleware writes a JSON audit line per call and feeds the counters /metrics reports.
A trace summary a model can afford
A single FleetAlert round writes a dozen or more spans with full inputs and outputs. get_trace folds them into one entry per round: entry point, outcome, proposed fix, model calls, tokens, cost and an ordered list of compact steps. get_raw_spans is there for the detail. Both carry JSON Schema output, generated from the same pydantic models the server validates against.
Tested without a network, then hosted for real
The SDK's in-memory client drives the real server against a mocked FleetAlert API, including the elicitation round trip for confirm_fix: approve sends exactly the token upstream, decline sends nothing, a client with no elicitation support is refused. Then the same server ran as a stdio subprocess, over HTTP with a bearer token being rejected and accepted, and finally as a Lambda behind API Gateway in the FleetAlert account, in stateless JSON mode with the elicitation state sealed by a shared key (fleetalert-mcp ADR-0005, fleetalert-ai ADR-0025). v0.3.0 added the OAuth sign-in, driven end to end through the same shim by the SDK's own OAuth client: register, consent, code, token, then tool calls as the visitor with reject_fix refused. The box above is that deployment.
Key decisions, from the record
By the numbers
Why an MCP server, and why this one
An MCP server is the current unit of integration for agent tooling, and most examples stop at wrapping an API. This one treats the protocol edge as a security boundary: what a model can discover, what a token can do, how often anyone can call, and which single action needs a human in the loop. The choices are written down as ADRs next to the code that enforces them, the way FleetAlert itself is documented.
It was built with Claude Code as a pair against the SDK v2 docs, which changed enough from v1 that recalled patterns were wrong; the server middleware, resolver-based elicitation and in-memory client were learned from the current reference and verified with tests before anything was written up.