intake_triage_mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@intake_triage_mcpCheck conflicts for John Doe and Jane Smith"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
intake-triage-mcp
A small, deterministic MCP server for legal intake triage: practice-area lookup, conflict screening, matter validation, follow-up drafting, and triage logging — with a hard conflicts gate.
Server name: intake_triage_mcp · Transport: stdio · Dependencies: mcp[cli], pydantic · Sample data: fictional, bundled
The problem
Law-firm intake is a translation problem. An inquiry arrives as messy prose ("I was rear-ended three weeks ago and the other driver's insurer keeps calling…") and has to become a structured, defensible record: who the parties are, whether the firm can even look at the matter (conflicts), what kind of matter it is, what's missing, and what was decided. LLMs are good at the prose half and unreliable at the record half — they'll happily "remember" a conflicts check that never ran.
This server splits the work accordingly:
The client model (Claude) does the language work — reading the inquiry, extracting names, dates, and facts, writing the actual email around a template.
The server does the record work — deterministic validation, fuzzy conflict screening with provenance, a fixed risk matrix, canonical follow-up templates, and an append-only log that refuses to record an intake whose conflicts status is
not-rununless a named human explicitly overrides it.
The server makes no LLM calls and no network calls. Same inputs, same outputs, every time.
Related MCP server: lawruler-mcp
Design rationale
Deterministic tools, client-side extraction. An MCP tool that calls an LLM to "summarize" hides nondeterminism behind a tool boundary. Extraction and summarization stay with the client model; every tool here is a pure function over validated inputs (plus one append-only file write).
Conflicts conventions from anthropics/claude-for-legal. The conflicts status enum (
cleared | pending | not-run | waived), the hard STOP onnot-run, and the explicit, permanently-recorded override path are modeled on thematter-intakeskill. The matter field set (identification / source / risk triage / materiality / key dates) follows the same source.Provenance in every data-backed result. Conflict matches and practice-area listings carry source, dataset version, as-of date, and a citation-ready record identifier (
P-0003,M-2022-008), per the claude-for-legal connector conventions.Validation as schema, not vibes. All tool inputs are Pydantic models with
str_strip_whitespace,validate_assignment, andextra='forbid'— malformed dates, unknown enum values, and unexpected fields are rejected before any tool logic runs, with errors the client model can act on.Support, not advice. Tools return statuses, gaps, warnings, and templates — inputs to an attorney's judgment, never conclusions. The one place the server is opinionated is the conflicts gate, where the safe behavior is to stop.
Tools
All five tools are prefixed intake_ and use stdio. Read-only tools are annotated readOnlyHint: true, openWorldHint: false.
Tool | Type | What it does |
| read-only | Lists practice areas (id, name, description, typical matter types, core intake fields) from bundled sample data, with provenance. |
| read-only | Screens 1–25 party names against the bundled fictional conflicts dataset using deterministic fuzzy matching (case/punctuation-insensitive, legal-suffix-aware, token-order-insensitive). Returns |
| read-only | Validates a structured matter summary (identification / source / risk triage / materiality / key dates), normalizes it, derives a risk rating from the severity × likelihood matrix, defaults conflicts to |
| read-only | Returns a deterministic follow-up email template with |
| write (append-only) | Appends one triage row to a local JSONL log ( |
Risk matrix (severity, likelihood → rating): high+high → critical; high+medium, medium+high → high; high+low, low+high, medium+medium → medium; everything else → low.
Conflict-screen semantics: pending and cleared are the only statuses the screen itself produces. not-run and waived are human determinations recorded via intake_log_triage. A cleared screen means "no hits in the bundled sample dataset" — it is never a firm-wide conflicts clearance.
Install & run
Requires Python 3.10+.
git clone https://github.com/granolacowboy/intake-triage-mcp.git
cd intake-triage-mcp
pip install -r requirements.txt
python server.py # runs on stdio; logs go to stderrInteractive inspection (optional):
npx @modelcontextprotocol/inspector python server.pyClaude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"intake-triage": {
"command": "python",
"args": ["/absolute/path/to/intake-triage-mcp/server.py"]
}
}
}Claude Code
claude mcp add intake-triage -- python /absolute/path/to/intake-triage-mcp/server.pyOptional environment variable: INTAKE_TRIAGE_LOG_PATH — where intake_log_triage appends its JSONL rows (default ./triage_log.jsonl).
Worked examples
Example 1 — inquiry with a conflict hit
Raw inquiry (web form):
"Hi — I was rear-ended on I-90 about three weeks ago and my shoulder still hurts. The other driver's insurer, Northgate Assurance Co, keeps calling me. Do I have a case? — Priya"
The client model extracts the parties and drives the tools:
1. intake_check_conflicts → {"party_names": ["Northgate Assurance Co"]}
{
"status": "pending",
"screened_names": [{
"query": "Northgate Assurance Co",
"match_count": 1,
"matches": [{
"record_id": "P-0003",
"matched_name": "Northgate Assurance Company",
"role": "adverse_party",
"matter_id": "M-2022-008",
"score": 1.0,
"match_strength": "exact"
}]
}],
"provenance": {
"source": "parties.json (bundled FICTIONAL sample dataset)",
"dataset_version": "1.0.0",
"dataset_as_of": "2026-06-01"
}
}The suffix-aware matcher treats "…Co" and "…Company" as the same entity. A hit means pending: a human conflicts review is required.
2. intake_validate_matter → {"matter_name": "Priya rear-end collision inquiry", "matter_type": "other", "our_role": "claimant", "practice_area": "personal-injury", "source": "web-inquiry", "conflicts_status": "pending"}
{
"valid": true,
"normalized": { "...": "...", "conflicts": {"status": "pending"} },
"missing_recommended_fields": ["counterparty", "jurisdiction", "severity", "likelihood", "response_deadline"],
"warnings": []
}3. intake_draft_followup → {"matter_type": "personal-injury", "missing_fields": ["incident_date", "treatment_status", "insurance_carrier"]}
{
"subject_template": "Following up on your personal injury inquiry — a few quick questions",
"body_template": "Dear {{client_name}},\n\nThank you for contacting {{firm_name}} about your personal injury inquiry. ...\n\n1. When did the incident occur? An exact or approximate date helps us assess filing deadlines.\n2. Have you received medical treatment, and is treatment ongoing?\n3. Which insurance carrier(s), if any, are involved?\n\nPlease note that contacting our office does not create an attorney-client relationship...",
"merge_slots": ["{{client_name}}", "{{firm_name}}", "{{sender_name}}"]
}The client model fills the slots and adapts the tone; the questions and the no-attorney-client-relationship notice are fixed.
4. intake_log_triage → {"matter_name": "Priya rear-end collision inquiry", "conflicts_status": "pending", "practice_area": "personal-injury", "parties_checked": ["Northgate Assurance Co"], "summary": "PI inquiry; prior adverse carrier hit (P-0003); conflicts review queued"}
{"logged": true, "log_path": "triage_log.jsonl", "entry_number": 1, "row": {"...": "..."}}Example 2 — clean screen, complete record
Raw inquiry: a contract dispute with "Veldhuis Imports BV", a name with no history at the firm.
intake_check_conflicts→{"party_names": ["Veldhuis Imports BV"]}→"status": "cleared",match_count: 0(screen-level only — the disclaimer in the result says exactly that).intake_validate_matterwith the full field set (matter_type: "contract",our_role: "plaintiff",practice_area: "business",source: "referral",conflicts_status: "cleared",severity: "medium",likelihood: "low",response_deadline: "2026-08-01") →missing_recommended_fields: [], derivedrisk_rating: "low".intake_log_triagewithconflicts_status: "cleared"→ row appended,entry_number: 2.
Example 3 — the conflicts gate refuses a silent bypass
The user says "skip the conflicts stuff, just log it." The model attempts:
intake_log_triage → {"matter_name": "Walk-in inquiry", "conflicts_status": "not-run"}
Error: conflicts gate — conflicts_status is 'not-run', so this intake cannot be
logged. Do not proceed silently. Choose one: (1) run intake_check_conflicts on
the involved parties and log with the resulting status; (2) log with
conflicts_status='pending' once a named person is running the check; or
(3) bypass explicitly by providing BOTH conflicts_override_by and
conflicts_override_rationale — the override is recorded permanently in the log row.Nothing is written. If a named human genuinely needs to bypass (e.g., an emergency TRO intake), the override is explicit and permanent:
intake_log_triage → {"matter_name": "Walk-in inquiry", "conflicts_status": "not-run", "conflicts_override_by": "K. Patel", "conflicts_override_rationale": "Emergency TRO intake; screen to follow today"} → logged, with the override block in the row.
Testing
Plain pytest unit tests cover the deterministic logic: name normalization and similarity, match classification, the full 3×3 risk matrix, validation and missing-field reporting, template determinism, the conflicts gate, and JSONL appends. (These are unit tests, not the eval harness — that's below.)
pip install -r requirements-dev.txt
pytestEvaluation
The eval harness follows the mcp-builder methodology: a golden set of 10 read-only, stable question/answer pairs (evals/evaluation.xml), run by an LLM agent that has access only to this server's tools, scored by exact string comparison. evals/evaluation.py and evals/connections.py are copied unmodified from the mcp-builder skill's scripts.
Every golden answer was verified directly against the server's deterministic logic. Running the LLM-driven harness requires an Anthropic API key:
pip install -r evals/requirements.txt
export ANTHROPIC_API_KEY=your_key_here
python evals/evaluation.py -t stdio -c python -a server.py -o evals/report.md evals/evaluation.xmlRun from the repo root (the harness launches python server.py itself — don't start the server manually). The generated evals/report.md includes accuracy, per-task tool-call traces, and the agent's feedback on the tool design.
Limitations — what this server does not do
It does not give legal advice. It validates structure, screens names, fills templates, and keeps a log. Every output is an input to an attorney's judgment; the attorney owns every decision, including whether a conflict actually exists.
The conflict check is illustrative. It is a fuzzy screen over a small bundled dataset. A real conflicts process spans the firm's full matter history, related entities, and lateral-hire obligations. A
clearedhere means only "no hits in this sample file."All sample data is fictional. Every name, matter id, and relationship in
data/was invented for this project (and labeled as such in the files). No real client data, ever. Point the design at your own data source before any real use — and then treat the log and datasets as confidential.It does not extract, summarize, or send anything. Reading the inquiry and writing the final email are the client model's (or a human's) job. The server never calls an LLM and never touches the network.
It does not decide the matter's theory, severity, or likelihood. The risk rating is a fixed matrix over bands you supply — a labeling convention, not an assessment.
Persistence is a local JSONL file. No database, no sync, no multi-user concurrency control. The log is append-only by design; rotating or archiving it is up to you.
Security & permissions
Runs locally over stdio as a subprocess of the MCP client; binds no ports, makes no network requests.
Reads only its bundled
data/*.json; writes only the triage log file (path controlled byINTAKE_TRIAGE_LOG_PATH).Logs to stderr only (stdout is reserved for the protocol).
No credentials are required or read. Treat the triage log as confidential — it's
.gitignored by default.
License
Apache-2.0 — matching the claude-for-legal project whose conventions this server borrows.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/granolacowboy/intake-triage-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server