Service request MCP tools

Five tools cover the inbound queue end to end. list_service_requests reads the queue, get_service_request reads one and returns the actions available on it, triage_service_request marks it under review, decline_service_request closes it with a client-visible reason, and create_proposal_from_service_request turns it into a draft quote. All five share one operations module with the operator UI, so agent and human triage produce identical records.

What are these tools for?

They expose a client's inbound service request queue — the work clients submit from their portal — to an assistant connected over MCP. Everything the operator can do to a request from the UI, an agent can do through these five tools, and the resulting record is identical.

For the concept, read how client requests close the loop. For the human path, read triage an inbound client request.

The five tools

ToolClassWhat it does
list_service_requestsreadThe queue, filterable by client, status, or awaiting_triage
get_service_requestreadOne request in full, plus next_actions
triage_service_requestwriteMarks it under review
decline_service_requestwriteCloses it with a client-visible reason
create_proposal_from_service_requestwriteTurns it into a draft proposal

Reading the queue

list_service_requests takes client_id, status, awaiting_triage and limit. Pass awaiting_triage: true rather than enumerating statuses — it covers new, pending and under_review in one call, and returns an awaiting_triage_count alongside the rows.

Each row carries the fields you need to decide without a second call:

{
  "reference": "REQ-XXXXXX",
  "status": "pending",
  "client_name": "…",
  "location": "12 Example Street, Suburb QLD 4000",
  "location_is_known_site": false,
  "site_id": null,
  "requested_address": "12 Example Street, Suburb QLD 4000",
  "description": "…",
  "urgency": "flexible",
  "age_days": 3
}

Read location_is_known_site on every row. When it is false, the client has named somewhere the business has no site for. Those requests cannot be quoted against an existing premises and usually need create_site first.

Reading one request

get_service_request(service_request_id) returns the full record plus a next_actions array — the actions legitimately available given the request's state. When the location is not a known site, create_site appears in that list before create_proposal_from_service_request, because quoting first produces a proposal with no premises attached.

Treat next_actions as the ordering instruction, not a menu.

Triaging

triage_service_request(service_request_id) sets the status to under_review, stamps the calling identity as the reviewer, and emits service_request.status_changed carrying that identity as the event actor. The client sees the status move in their portal.

Declining

decline_service_request(service_request_id, reason) requires a non-empty reason, and rejects whitespace. That is not input hygiene — the reason is rendered to the client in their portal, so write it in the words you would say to them.

The call sets the status, stores the reason, stamps the reviewer, emits the status-change event with the reason in its payload, and writes an audit row.

Quoting

create_proposal_from_service_request(service_request_id) creates a draft proposal, links it to the request, and moves the request to quoted.

The proposal inherits the client, the site (when the request named one), the client's purchase-order reference, the description as notes, and — when the location is an address rather than a site — that address written into the notes so it cannot be lost.

The response tells you what you are holding:

{
  "proposal_id": "…",
  "line_count": 0,
  "requested_address_needs_a_site": "12 Example Street, Suburb QLD 4000",
  "next_step": "add_proposal_line — this proposal has NO lines and would send as an empty quote"
}

It prices nothing. A request naming a catalogue service yields one line at zero; a request describing something not in the catalogue yields no lines at all. Read line_count before going anywhere near propose_send_proposal.

If the line insert fails, the proposal is deleted and the request is left untouched — the call fails rather than leaving a half-made quote in the pipeline.

Why agent and human triage agree

The three write tools and the operator UI call one shared operations module. Neither surface owns a private copy of the rules.

This matters more than it sounds. When the same logic is implemented twice, the two copies drift in the details nobody asserts — which field gets stamped, which identity is recorded, which column is carried onto the next record. A single module makes "the agent did it" and "the operator did it" produce byte-identical records, and makes a test of one a test of both.

Related

Last updated 2026-08-16