Job orders for trades and field work: parts and labour as they happen, a status that moves one dated step at a time, a completion report, and an invoice payload that needs no retyping. You raise a work order against a client, and the client is the one the invoice server already knows, looked up by name in its own records rather than spelled a second way here, so the job and the invoice carry the same customer. It gets a site address, the date it was asked for, what the job is and how urgent, and a WO-YYYY-NNNN number. Then you log what the job actually uses as it goes: labour as hours at an hourly rate, parts as a quantity at a unit cost in minor units with an optional markup percent. The markup goes on the UNIT cost and never on the line total, which sounds like a detail and is the decision the whole server rests on, because the invoice server rounds a unit price into minor units first and computes the line from that stored value: the marked-up unit is the only basis an invoice can reproduce. The status moves one step at a time, draft to scheduled to in progress to done to invoiced, each step stamped with its own date and a note, and a skipped step is refused naming the step that IS next, because a job that went from scheduled straight to invoiced was never marked done, so no completion report was ever produced and nothing in the record says the day the work finished. Backwards is refused too: a job that has to go back is a new work order, and the history of the first one has to stay true. Nothing is stored twice: the value, the hours, the materials and the VAT are derived from the lines on every call, because a stored total is a second copy of what the lines already decide and the copy is the one that gets believed after somebody edits a line. Hours are counted on the LINE date rather than the order date, so a February call worked in March logs its hours in March. When the job is done you hand the customer a completion report with the hours, the materials, the totals and a sign-off block, and take work_order_invoice_payload straight to invoice_create: it posts nothing, marks nothing, and its totals are computed by the invoice server's own function over the very items it is handing you, so there is no second implementation to disagree.
In agent mode work_order_invoice_payload is a pure derivation that posts nothing and marks nothing: it returns the invoice_create arguments and says so, posted false and marked_invoiced false. Running invoice_create in the invoice server and then setting this job to invoiced are two separate steps on purpose, because an agent that treats the payload as the invoice has billed nobody while the board says it has, and an order already marked invoiced refuses a second payload by name so the other mistake cannot happen twice.
| You say | Tool |
|---|---|
| Raise a work order for Harbour Cafe, site 12 Quay Street, requested 2026-03-06, urgent: the walk-in is not holding temperature. | work_order_create |
| Three hours labour on 2026-03-08 at 8,500 an hour, and seven thermostats at 1,299 each with 15 percent markup. | work_order_add_line |
| Mark it done on 2026-03-09, then give me the completion report and the invoice payload. | work_order_status |
The markup basis is worth exactly one minor unit at a time, and one minor unit is enough to make an invoice disagree with the job it came from. Measured on this server's own engine: seven parts at a unit cost of 1,299 with 15 percent on top is roundHalfUp(1299 * 1.15) = 1,494 a unit and 10,458 on the line. Marking up the line total instead is roundHalfUp(9093 * 1.15) = 10,457. Both are defensible arithmetic, both reconcile against their own workings, and only one of them is what the customer is billed, because the invoice server stores a rounded unit price and computes the line from it. The unit suite asserts that gap is exactly 1, so a change of basis fails the build rather than quietly re-pricing every job on the board. The worked job then runs the whole way through: the payload's items are handed back to the invoice server's own computeTotals from the test process and come back at net 59,833, VAT 13,762 and total 73,595, with rounding drift of zero.
The file is .vscode/mcp.json, and the key inside it is servers.
| Scope | Path |
|---|---|
| This workspace | <workspace>/.vscode/mcp.json |
| Every workspace | the user profile mcp.json, opened by the MCP: Open User Configuration command |
// .vscode/mcp.json (the key is "servers")
{
"servers": {
"work-order": {
"command": "npx",
"args": ["-y", "@theluckystrike/mcp-work-order"]
}
}
}
Or from a terminal:
code --add-mcp '{"name":"work-order","command":"npx","args":["-y","@theluckystrike/mcp-work-order"]}'
VS Code asks you to confirm you trust the server and its capabilities before it starts. Nothing runs until you answer.
The npm publish of @theluckystrike/mcp-work-order is pending; until then use the .mcpb bundle or a clone and build from the latest release.
The same server runs at https://mcp.zovo.one/mcp/https://mcp.zovo.one/mcp/work-order over MCP streamable HTTP, no install. Mint a free token with curl https://mcp.zovo.one/mcp/token, or use a Pro key. It has no filesystem, so a file comes back as a one-hour download link.
{
"servers": {
"work-order": {
"url": "https://mcp.zovo.one/mcp/https://mcp.zovo.one/mcp/work-order",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
The top-level key is "servers", not "mcpServers". A config pasted from a Claude Desktop or Cursor README parses cleanly, contributes no servers, and gives you no error to read, which makes it the most expensive one-word mistake here. Alongside it an "inputs" array holds secrets. The newer Agent Host reads a workspace .mcp.json instead.
Free: Five OPEN work orders, which is a one-van trade, and 200 lines on each of them on every tier. The cap counts open jobs, draft, scheduled and in progress, rather than jobs ever raised, so finishing a job frees its slot: the free tier does not fill up with history. work_order_delete on a draft with no lines is free on every tier as well, because a way back that only a Pro key can reach is not a way back. The text completion report is free too: handing the customer a written record of what was done is the thing the job was for, not an upsell. A byte-identical work order is refused before the cap is even consulted, so a double-typed job names the id already stored rather than being met with an upgrade prompt, and it burns neither a slot nor a WO number. Pro is $19 once, verified offline.
The key is servers, not mcpServers. A config copied from a Claude Desktop README parses cleanly and contributes nothing. Rename it. On the newer Agent Host, use a workspace .mcp.json instead.
No. code --add-mcp takes the server object on the command line, and MCP: Add Server in the palette asks whether the target is Workspace or Global.
In Chat, behind the tools picker, once the server has started. Adding or changing one raises a prompt asking you to confirm you trust it; if the picker is empty, that prompt is usually still waiting.
MCP Work Order in detail · The same server in Cursor and Windsurf · Every server in VS Code · Guides · VS Code docs · Buy Pro