ClaimPilot exposes its claim intake and triage as a Model Context Protocol (MCP) server, so an external AI client (Claude Desktop, Claude Code, the MCP Inspector, or any MCP-capable tool) can create claims, poll their status, read the adjuster-facing claim card, and browse the claim list — the same operations a human gets from the web form and Filament admin.
This page is also published as a rendered web page on the live site at https://strahovanie.up-money.ru/docs/mcp (served by
App\Http\Controllers\DocsControllerfrom the bundled copy atapp/resources/docs/mcp.md).
MCP is model-agnostic. The client is the host application / agent framework that speaks MCP — not the LLM itself. An agent built on any model — including Russian models such as GigaChat or YandexGPT — can connect to this server, provided its host/framework implements MCP. Named ready-made MCP clients today are tools like Claude Desktop, Claude Code, the MCP Inspector, and Cursor; GigaChat/YandexGPT do not ship a native MCP client, but an agent you build around them can act as one.
This is a server, not an agent: ClaimPilot publishes tools; the client is the third-party AI tool. ClaimPilot itself never calls out to an LLM on the client's behalf. (The triage pipeline does use LLMs internally — see pipeline.md — but that runs server-side and is fully audited regardless of how the claim was created.)
MCP is self-describing: the client learns everything from the server's instructions, tool
descriptions, and input schemas at connect time. The shapes below mirror the code exactly
(App\Mcp\*); the shared formatter App\Mcp\Support\ClaimPresenter guarantees code and docs
do not drift.
Built on the official laravel/mcp package. Server class:
App\Mcp\Servers\ClaimPilotServer.
| Tool | Annotation | Purpose |
|---|---|---|
submit_claim |
state-changing (not read-only) | Create a claim and start the triage pipeline. |
get_claim_status |
read-only | Status + completed pipeline stages for one claim. |
get_claim_card |
read-only | Full adjuster-facing claim card. |
list_claims |
read-only | List claims, with Filament-style filters. |
mark_reply_sent |
destructive, idempotent | Stamp the reply draft as sent. |
submit_claim (state-changing)Creates a claim with status = received, channel = mcp, and dispatches the same
App\Services\ClaimPipeline chain the web form uses. Returns immediately with the claim
uuid — processing is asynchronous (see Async model). Validation rules are the
same as the web form (ClaimIntakeController::textFieldRules()), not a copy.
Input (all required):
| Param | Type | Notes |
|---|---|---|
claimant_name |
string | max 255 |
claimant_contact |
string | phone or email, max 255 |
incident_description |
string | 10–10000 chars, free text (any language) |
incident_date |
string | YYYY-MM-DD, today or earlier |
Returns { uuid, status, message } — status is "received"; message tells the client to
poll get_claim_status.
No file upload. MCP tool arguments are JSON; photos and document scans are binary. For phase 1,
submit_claimis text-only. Damage photos and policy scans still go through the web form, which feeds the vision stages. A text-only claim runs the full pipeline — the photo/document stages simply have nothing to analyze, and synthesis works from the narrative alone (often landing inneeds_info). Binary intake over MCP is a deliberate phase-2 item.
get_claim_status (read-only)Input: uuid (string, required) — public claim reference returned by submit_claim.
Returns:
{
"uuid": "…",
"status": "received|processing|triaged|needs_info|failed",
"stages_completed": ["vision_photos", "vision_documents", "synthesis", "notify"],
"stages_pending": [],
"is_finished": true
}
is_finished is true once status is triaged, needs_info, or failed. If status stays
received with nothing in stages_completed, the queue worker is probably not running.
get_claim_card (read-only)Input: uuid (string, required).
Returns:
{
"uuid": "…",
"status": "triaged",
"claimant_name": "…",
"claimant_contact": "…",
"incident_type": "auto_accident|property_water|property_fire|theft|health|other|null",
"severity": "minor|moderate|major|total_loss|null",
"severity_confidence": 0.82,
"estimated_amount_min": 40000,
"estimated_amount_max": 120000,
"ai_summary": "…",
"client_reply_draft": "…",
"red_flags": [{ "code": "…", "severity": "low|medium|high", "explanation": "…" }],
"missing_documents": ["…"]
}
Amounts are in RUB and may be null (the model estimates conservatively). incident_type,
severity, and the amounts are null until synthesis has run.
list_claims (read-only)All inputs optional; filters combine with AND; newest first.
| Param | Type | Allowed values |
|---|---|---|
status |
string | received, processing, triaged, needs_info, failed |
incident_type |
string | auto_accident, property_water, property_fire, theft, health, other |
severity |
string | minor, moderate, major, total_loss |
limit |
integer | 1–100, default 25 |
Returns a JSON array:
[{ "uuid": "…", "claimant_name": "…", "incident_type": "…", "severity": "…",
"status": "…", "red_flag_count": 1, "created_at": "2026-06-18T16:00:00+00:00" }]
mark_reply_sent (destructive, idempotent)Input: uuid (string, required).
Sets client_reply_sent_at to now if not already set (same as the Filament "mark as sent"
button). Idempotent: calling it again keeps the original timestamp.
Returns { uuid, client_reply_sent_at } — client_reply_sent_at is ISO-8601.
Read-only context the client can pull without invoking a tool:
| Name | URI | MIME | Content |
|---|---|---|---|
claim_card |
claimpilot://claims/{uuid} |
application/json |
Same payload as get_claim_card. |
The resource and the get_claim_card tool share ClaimPresenter::card(), so they never drift.
(The ai_calls audit log is intentionally not exposed over MCP — it can contain raw provider
responses; it stays admin-only in Filament.)
| Name | Arguments | Purpose |
|---|---|---|
review_claim |
uuid (required) |
An adjuster-style review prompt: loads the claim card and asks the model to assess narrative/evidence consistency, validate the red flags, judge the RUB estimate, and critique the Russian reply draft, ending with an approve / request-info / escalate recommendation. |
submit_claim returns as soon as the claim row is created and the chain is dispatched — it does
not wait for triage. The pipeline (photos → documents → synthesis → notify) runs in the
queue. Clients should poll get_claim_status until is_finished, then read get_claim_card.
A queue worker must be running, or the claim stays in
receivedforever:php artisan queue:work --tries=1 --timeout=900
Tools return a clear, actionable error (not a generic 500) the model can act on:
No claim found with uuid [X].Mcp::local('claimpilot', …), run via php artisan mcp:start claimpilot.
Runs as a child process of the client on the same machine; no auth needed. This is the demo /
single-developer setup.Mcp::web('/mcp/claimpilot', …) must sit behind authentication. ClaimPilot
has a single admin user, so the example in routes/ai.php is guarded with
->middleware('auth:sanctum') and is commented out by default. Do not expose the web
variant unauthenticated — every tool reads or mutates real claim data.cd app
php artisan mcp:inspector claimpilot
Lists all tools/resources/prompts and lets you call them interactively.
From the app directory, register the local server (stdio):
claude mcp add claimpilot -- php /absolute/path/to/app/artisan mcp:start claimpilot
…or commit a project-scoped .mcp.json:
{
"mcpServers": {
"claimpilot": {
"command": "php",
"args": ["artisan", "mcp:start", "claimpilot"]
}
}
}
Add to claude_desktop_config.json (use absolute paths — Desktop does not inherit your shell):
{
"mcpServers": {
"claimpilot": {
"command": "php",
"args": ["/absolute/path/to/app/artisan", "mcp:start", "claimpilot"]
}
}
}
A queue worker still has to be running separately for submitted claims to progress past
received.