MCP server › Sample workflows

Onboard a custodian to legal hold

A custodian joins a legal matter: scope a collector to just their mailbox, run it, and file the per-account completion report as evidence the collection happened. For legal hold, “we collected it” has to be demonstrable per custodian, not just per job — the report in step 6 is the deliverable. The sample runs against the smallest mailbox in the DataTap-hosted Enron corpus (Jay Reitmeyer, 892 items) so the run finishes quickly. Steps 1–2 are read-only; steps 3–6 create and run a real collector.

The tool sequence

Steps 1–2 resolve the custodian to collect. Step 3 is a single composite call that both creates the collector and starts it. Step 4 polls to completion, and steps 5–6 pull the evidence report:

// 1 · Check whether distribution-list scoping is available for this
//     tenant. Envelope is { Options, Sources } — distinct from the
//     { Items, Options, Total } browse page used elsewhere.
{"tool": "discover_groups", "arguments": { "connectionId": "<exchange-connection-id>" }}
// → if Sources is empty (no real DLs), fall back to discover_users below.

// 2 · Resolve the custodian by name.
{"tool": "discover_users", "arguments": {
  "connectionId": "<exchange-connection-id>",
  "nameFilter": "<custodian-name>",
  "includeSharedMailboxes": true
}}
// → Sources[] entries carry Id ("Mailbox|<guid>"), Name, Email, Type —
//   the four fields collect_email needs on each users[] entry.
//   includeSharedMailboxes is required to see shared mailboxes.

// 3 · Create the collector AND start it in one call, scoped to just
//     that custodian.
{"tool": "collect_email", "arguments": {
  "name": "<collector-name>",
  "sourceId": "<exchange-connection-id>",
  "destinationId": "<blob-connection-id>",
  "runNow": true,
  "users": [{
    "id": "<Id-from-step-2>",
    "name": "<Name-from-step-2>",
    "email": "<Email-from-step-2>",
    "type": "<Type-from-step-2>"
  }]
}}
// → { collector: { Id }, run: { TaskId } } — both halves come back from
//   one call when runNow is true.

// 4 · Poll the run to a terminal state.
{"tool": "get_collector_status", "arguments": {
  "collectorId": "<collector.Id>",
  "taskId": "<run.TaskId>"
}}
// → State: RUNNING → READY | FAILED | STOPPED

// 5 · Once READY, find the completed report for this job.
{"tool": "list_collector_reports", "arguments": { "jobId": "<collector.Id>" }}

// 6 · Pull the per-custodian evidence row from that report.
{"tool": "get_collector_report_accounts", "arguments": { "reportId": "<reportId-from-step-5>" }}
// → { TotalItems, TotalBytes, State: "SUCCEEDED" } per account

The composite contract

collect_email with runNow: true returns both the new collector's Id and the started run's TaskId in one response — an agent does not need a second call to run_collector just to learn what task to poll. discover_users hands back node ids already prefixed with their type (Mailbox|<guid>); that prefix is stripped server-side, so the same value can be passed straight through to collect_email's users[].id without editing it.

Reading the completion report

get_collector_report_accounts normalizes the underlying report fields to TotalItems and TotalBytes, and derives a State of SUCCEEDED for a clean run — the raw Count/BackupSize field names are not present in the tool's response, so a caller reading the normalized names cannot accidentally miss data under the old ones. For a single-custodian collector, the report has exactly one account row, and re-collecting a mailbox the corpus already holds overwrites that account's record rather than appending a duplicate.

Extend it. Pass multiple entries in collect_email's users array to onboard several custodians to the same collector at once, or use get_running_collector_accounts instead of polling get_collector_status to watch per-account progress while a multi-custodian run is still in flight.

← Previous sample: Export a message for legal hold · Back to MCP server →