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
| Tool | Class | What it does |
|---|---|---|
list_service_requests | read | The queue, filterable by client, status, or awaiting_triage |
get_service_request | read | One request in full, plus next_actions |
triage_service_request | write | Marks it under review |
decline_service_request | write | Closes it with a client-visible reason |
create_proposal_from_service_request | write | Turns 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.