Now domain-agnostic — configure any practice with Domain Packs (immigration is the reference pack)
MCP Surface

MCP Surface

Audience: Engineers integrating an AI agent with the server's tool surface.
What you will accomplish: Understand every tool, resource, and prompt the MCP server exposes — their inputs, outputs, risk tiers, and idempotency contracts.
Prerequisites: Read Architecture first.
Estimated time: ~10 minutes.

The MCP server (cam.mcp_server) publishes the full operational surface as tools (actions), resources (read-only context), and prompts (reusable templates). Every primitive is self-describing via Pydantic v2 schemas — the client learns the interface automatically.

What is the MCP surface?

The MCP (Model Context Protocol) surface is the complete set of operations an AI agent can perform. It has three parts:

  • Tools — actions the agent can take (read data, create records, draft emails, run workflows)
  • Resources — read-only context the agent can pull on demand (matter details, templates, deadline rules)
  • Prompts — reusable reasoning templates for common tasks (intake interview, privilege review)

Every tool is tagged with a risk tier (read, write-confirm, or gated-human) that determines whether human approval is needed before execution. The client learns the interface automatically from Pydantic v2 JSON schemas — no manual integration required.

Tools

Tools are thin wrappers: validate input → call core service → write audit → return structured output. They carry no business logic of their own.

Matter management

ToolInputOutputRisk tier
matter.getid: strMatterread
matter.searchquery: MatterSearchQuerylist[Matter]read
matter.createdata: MatterDraftMatterwrite (confirm)

Contact management

ToolInputOutputRisk tier
contact.upsertcontact: ContactContactwrite (confirm)
contact.findquery: strlist[Contact]read

Document operations

ToolInputOutputRisk tier
document.generatematter_id: str, template_name: str, data_context?: dict, doc_key?: str, idempotency_key?: str, render_pdf: bool = trueGenerationResultwrite (confirm)
document.routedocument_id: str, recipient_id: str, intent: RoutingIntentRoutingResultwrite (confirm) / gated (human)
document.extractinput_ref: str, content_kind: "pdf" | "image" | "email_body", mapping_profile?: str, threshold?: float, structuring: bool = true, async_ok: bool = trueExtractionProposal | {run_id, status_resource}read

Communication

ToolInputOutputRisk tier
email.draftmatter_id: str, template: str, context: dictCommunicationdraft only
email.senddraft_id: str, idem_key: strstr (message_id)gated (human)
email.send is default-gated

email.send is the only tool in the default set that carries the gated (human) tier. No approval token can override this — the gate is a safety invariant. Drafting is unrestricted; sending requires explicit human approval.

Deadline management

ToolInputOutputRisk tier
deadline.computerule_id: str, jurisdiction: str, trigger_inputs: dict, practice_area?: str, as_of?: datetimeDeadlineComputeResultread
deadline.schedulematter_id: str, rule_id: str, jurisdiction: str, trigger_inputs: dict, name: str, idempotency_key: str, practice_area?: strScheduledDeadlinewrite (confirm)

Workflow control

ToolInputOutputRisk tier
workflow.runworkflow: str, context: dict, trigger?: str, idem_key?: strWorkflowRunvaries by workflow
workflow.statusrun_id: strWorkflowRunStatusread
workflow.approve`run_id: str, token: str, decision: "approve""reject"`ApprovalDecision

QC & Forms

ToolInputOutputRisk tier
qc.verifypacket: VerificationPacket, checks?: list[str]QCReportread (gate input)
form.prefillmatter_id: str, form_id: str, data_context?: dict, emit: "fields_only" | "document"PrefillResult | GenerationResultread (emit="fields_only") / write (confirm) (emit="document")

Intake

ToolInputOutputRisk tier
intake.runlead: LeadPayload, idem_key?: strIntakeResultcomposite (confirm)

Resources

Resources are read-only context the agent can pull on demand. They return structured data, not natural language.

ResourceURI patternReturns
Matter viewmatter://{id}Full matter (contacts, docs, deadlines, comms)
Templatetemplate://{name}Template spec + required variables
Deadline rulesdeadline-rules://{jurisdiction}Active rule sets for jurisdiction
Calendar upcomingcalendar://upcoming?window={days}Deadlines/tasks due in window
Audit log sliceaudit://{run_id}Audit records for a workflow run
Connector healthhealth://connectorsCircuit state + last error per connector

Prompts

Prompts are reusable, versioned prompt templates for common reasoning tasks.

PromptPurposeVersion
intake_interviewStructured client-intake questioningv1
status_update_emailOn-brand status update from matter deltav1
qc_checklistVerification reasoning scaffoldv1
extraction_schemaGuide LLM extraction into domain modelv1
privilege_reviewDecision scaffold for privilege classificationv1

Each prompt carries a version hash. The workflow engine records the prompt version on every run that uses it, so output quality can be traced back to the exact prompt text.

Schema self-description

Every tool's input and output is a Pydantic v2 model. The MCP server automatically generates JSON Schema from these models, so the client knows:

  • Required vs optional fields
  • Field types and constraints
  • Enum values
  • Nested object shapes

Example: matter.create input schema (excerpt)

{
  "title": "MatterDraft",
  "type": "object",
  "required": ["reference", "title", "client", "status"],
  "properties": {
    "reference": { "type": "string", "description": "Firm-assigned matter reference" },
    "title": { "type": "string", "description": "Short descriptive title" },
    "client": { "$ref": "#/definitions/Contact" },
    "status": { "type": "string" },
    "practice_area": { "type": "string", "enum": ["family-based", "employment-based", "other"] },
    "responsible": { "type": "string", "description": "Attorney id or name" }
  }
}

Tool risk classification

The server tags every tool with a risk_tier enum. The client (or a policy layer) can use this to decide whether to invoke the tool automatically or require human confirmation.

class RiskTier(str, Enum):
    READ = "read"                # No side effects
    WRITE_CONFIRM = "write_confirm"  # Reversible, one-click confirm
    GATED_HUMAN = "gated_human"      # External/irreversible, explicit approval

Idempotency key contract

Every write tool accepts an optional idem_key. If provided:

  • The engine checks the IdempotencyStore (Redis-backed) for a previous execution
  • Hit → returns the stored output without re-effect
  • Miss → executes, stores the output, and returns

This gives exactly-once semantics even under at-least-once delivery.

Example: calling intake.run

Python — calling intake.run
uv run python -c " from cam.core.workflows.intake import tool_intake_run from cam.core.workflows.intake.types import LeadPayload import asyncio  result = asyncio.run(tool_intake_run( lead=LeadPayload(channel=\"web_form\", raw={\"email\": \"client@example.com\", \"name\": \"Jane Doe\"}), idem_key=\"intake:web:2026-06-01:jane-doe\" )) print(result) "

Expected output:

{
  "run_id": "run_abc123",
  "status": "awaiting_approval",
  "steps_completed": ["parse_lead", "dedupe_contact", "create_contact", "create_matter", "compute_deadlines", "open_tasks", "draft_welcome"],
  "gate": {
    "step": "GATE:human_review",
    "channels": ["mcp", "web", "email"],
    "ttl_seconds": 86400
  },
  "idempotency_key": "intake:web:2026-06-01:jane-doe"
}

The run is parked at awaiting_approval. The welcome email will not send until an approver calls workflow.approve with a valid token.

Common mistakes

MistakeConsequenceFix
Calling email.send without pre-draftingThe tool requires a draft_id; direct send is not possibleAlways call email.draft first, then email.send with the draft ID
Omitting idem_key on write toolsRetries may double-create or double-sendAlways provide an idempotency key on write and gated tools
Assuming workflow.approve can override privilege gatesThe privilege gate is checked before the approval gate and cannot be bypassedVerify document privilege status before attempting approval

Verification

After reading this page, verify your understanding:

  1. Name the three risk tiers and which ones require human involvement
  2. Explain what happens when you provide the same idem_key twice to matter.create
  3. List the three approval channels available for gated tools

Next steps


Last updated: 2026-06-01