WorkMobile MCP server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@WorkMobile MCP serverWhat's waiting for my approval on the Customer Visit Record form?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
WorkMobile MCP server
An MCP server that lets Claude, ChatGPT and other MCP clients work with a WorkMobile account: jobs dispatched to field staff, job types, forms and the records submitted on them, approvals and mobile users, and (when enabled) creating and allocating jobs and approving or rejecting records. It is built from WorkMobile's public documentation only: the Web API v2 OpenAPI spec, the WorkMobile help centre's API 2.0 articles, and the Power Automate connector definition that eSAY Solutions Ltd publishes (the source of the response schemas; the v2 spec has none for these endpoints).
Once it's connected, someone in the office can ask things like:
"Which jobs are booked for Sam tomorrow, and which are still unallocated?"
"Show me the history of job 70001."
"What came in on the Gas Meter Inspection form this week where the meter type was Gas or Water?"
"What's waiting for my approval on the Customer Visit Record form?"
With writes enabled: "Raise a meter read job at 3 Example Lane for Friday and give it to Sam", "Approve record 30000 on the inspection form."
Tools
Tool | What it does | API calls |
| Searches jobs by status IDs, estimated start date range, the mobile users they are allocated to, job types, description, priority, backlog and whether a duration is set; optional sort by estimated start. Pages of 500 (the smallest page size the API allows). A POST that only reads. |
|
| One job: description, status, type, priority, dates, allocation, history (status changes with narrative) and job data. |
|
| WorkMobile's static list of job status IDs, returned as WorkMobile sends it (the format is not documented). |
|
| Job types with the form each uses, default duration and location settings. |
|
| One job type and the fields a job of that type takes (name, type, required, allowed values, limits), read from its JSON Schema document. |
|
| Forms visible to the API user (sub-forms hidden unless asked). The API returns the whole list without paging. |
|
| Searches the records submitted on one form by created or uploaded date, job, mobile user, user group, or searchable field values with the documented operators; newest first by default. Pages of 500. A POST that only reads. |
|
| One record by its Id (the search's |
|
| Without |
|
| Field staff with name, job title, user group name and whether active (inactive users hidden unless asked). |
|
| Raises a job of one type, optionally allocated to one mobile user. Fetches the job type's schema first and refuses locally when a data field is unknown, a required field is missing, a value is not an allowed one, a numeric field gets text, or only one of latitude/longitude is given. Only registered when writes are enabled. |
|
| Allocates or reallocates a job to one mobile user, replacing any previous allocation. Marked destructive. Writes only. |
|
| Approves the current approval step of a record as the API user. Cannot be undone. Marked destructive. Writes only. |
|
| Rejects a record's current approval step. Rejection is terminal: the workflow stops for good. Marked destructive. Writes only. |
|
The v2 API is the whole web application's API (158 operations). Not covered on purpose: the username/password login (/api/account/authenticate) and every other account, password, SSO and API-token route; CRM customers and contacts and lone worker event logs (their read endpoints exist, but WorkMobile publishes neither a response schema nor an example for them, so their field names would have to be guessed); record revision and approval history (same reason); reports, media attachments, resources and every other download; track-worker locations; notification history; filtered views, menus and push notifications; user (portal login) management; job editing, closing, unallocation, group broadcast and routing; and every delete endpoint.
Related MCP server: Amiqus MCP server
Setup
Requires Node 18 or later.
npm install
npm run buildYou need an API token for your WorkMobile account. WorkMobile's help centre recommends it for unattended use: create a portal user with suitably limited access on the Logins page, then click Generate in its API Token section and copy the GUID. The token gives non-expiring access until it is revoked or regenerated. The server sends it in the X-API-Key header on every request; it never uses a username or password.
Claude Desktop: add this to claude_desktop_config.json:
{
"mcpServers": {
"workmobile": {
"command": "node",
"args": ["/absolute/path/to/workmobile-mcp/dist/index.js"],
"env": { "WORKMOBILE_API_KEY": "your-api-token" }
}
}
}Claude Code:
claude mcp add workmobile -e WORKMOBILE_API_KEY=your-api-token -- node /absolute/path/to/workmobile-mcp/dist/index.jsVariable | Required | Meaning |
| yes | The API token (a GUID) of a portal user, sent as the |
| no |
|
| no | Defaults to |
Safety defaults
Read-only unless
WORKMOBILE_ALLOW_WRITES=true. Every read tool carries the MCPreadOnlyHintannotation, including the ones that call a search endpoint withPOST(list_jobs,list_form_submissions,get_form_submission, andlist_approvalswith aform_id): WorkMobile runs its job and record searches asPOSTrequests with a criteria body, and they change nothing.allocate_job(it replaces the job's current allocation),approve_recordandreject_record(they move an approval workflow on for good) carrydestructiveHint: true, since MCP reservesfalsefor tools that only add;create_job, which only adds a job, is a write that is not destructive.Personal data is withheld by default and returned only when a tool is called with
include_contact_details=true:Job data and form answers are keyed by the account's own field names, so fields are classified by name. A field whose name mentions an email, phone, mobile or "mob", postcode, address, street, what3words, customer, client, tenant, resident, patient, witness, next of kin, name (any "name", so "Site Name" too), signature, photo, image, sketch, video, audio, attachment or file, location, GPS, latitude/longitude, date of birth (including "DoB" and "D.O.B"), passport, NHS or national insurance number (including "NINumber" and "NINo"), vehicle registration, injury, medical, medication, allergy, disability, illness or diagnosis, salary, wage, earnings, overtime, pay, paid or rate, or contact/person/owner/driver is replaced with
[withheld: personal data; …]. Names are matched in any style: PascalCase unique names as WorkMobile's documentation writes them ("MeterType"), acronyms ("GPSCoords"), and labels with spaces, dots or hyphens. "Health" alone is not a trigger, because "Health and Safety" fields are common in field-service forms. Nested values are checked the same way.A job's
Location(its address) is withheld; a mobile user'sUsername(often an email address) is not returned.In every other text (job descriptions and history narratives, answers in fields with ordinary names, names and job titles, form descriptions, job type names, WorkMobile's own error messages, the job status list, the approval inbox totals) email addresses become
[email redacted], phone-number-like sequences[phone redacted], latitude/longitude pairs[location redacted]and UK postcodes[postcode redacted]. The phone match is the same heuristic as the other prototypes: international numbers with+or00, UK numbers with a bracketed area code, and UK-style0…numbers of 9 to 11 digits; other digit strings starting with0are caught too. The postcode match is upper case only. People's names and street addresses written in free text are not recognised: "lives at 14 Example Road" comes back as written (only a postcode after it is redacted), and so does a customer's name in a job description or history narrative.A value that is a bare link (
http…orwww.…) in job data or form answers is withheld, because it may point to a photo, signature or file.Staff names are returned: mobile users' names,
CreatedByon records and job history, and the approval columns of a record. A record'sCustomercolumn, like any field with "customer" in its name, is withheld. A system column (such asCreatedByorUserGroup) or a jobLocationthat arrives as an object or an array rather than text is walked like job data: its keys are classified by name and its text redacted.
Never returned, whatever is asked: fields whose name mentions a bank, sort code, IBAN, SWIFT/BIC, account number ("Acc No", "Acct"), card, CVV/CVC, expiry, PAN, last 4 or last four, or payment; any 13-to-19-digit number that passes the Luhn check in any text (replaced with
[card number redacted]); in any text, an IBAN that passes its mod-97 check, and a sort code, account number or card's last four digits when the words in front name them ("sort code 12-34-56", "acct 12345678", "card ending 4242") (replaced with[bank details redacted]; a bare 6- or 8-digit number is left alone); and any embedded file (adata:URI, as a signature or photo could be sent) (replaced with[embedded file omitted; files are never returned]). No file is ever downloaded: the server never calls the report, media, resource or download endpoints.IDs are checked before any call is made: every ID must be a whole number from 1 to 2147483647 (the spec types them as int32, except
FormIdin the path of the records search, which it types as a string; form IDs are int32 everywhere else). Dates takeYYYY-MM-DD(real calendar dates only; an impossible month or day is refused with "Not a real calendar date"), which becomes the start of that day in UTC for a "from" and the end for a "to", or an ISO 8601 date-time with a zone (Zor an offset) and an hour from 00 to 23, as in WorkMobile's documented examples. Field filter operators must be one of the eleven the search documentation lists.WorkMobile does not document a rate limit (nothing in the v2 spec, the connector definition or the help centre's API 2.0 articles). Requests are spaced 250 ms apart (about four per second). A 429 is retried at most twice for any method, on the assumption that a rate-limited request was not processed, waiting for
Retry-After(whole or fractional seconds, or an HTTP-date; 2 s then 4 s when the header is absent or unreadable). Each wait is capped at 10 seconds; if WorkMobile asks for a longer wait the call gives up at once and the message says how long to wait. A tool call that makes several requests (up to 10 pages) can still run past the MCP client's default 60-second request timeout.502, 503 and 504 are retried the same way for
GETonly; when all three attempts fail the error says the service may be unavailable, without the gateway's HTML. NoPOSTis ever retried after a gateway error. For the searches the message says the search only reads, so calling the tool again is safe; for a write it says the request may already have been processed and names the tool to check with (list_jobs,get_joborlist_approvals) before repeating it.A 200 whose body is not JSON (a proxy or a login page in the way) is reported as an error naming
WORKMOBILE_BASE_URL, never as an empty list; the body is described by type and size, never quoted. A search that answers with anything other than a JSON array of rows is reported with the key names it got, never read as empty. The connector definition types the job search answer and the job type schema document as strings, so a JSON array or object sent double-encoded (as a JSON string holding JSON text) is decoded once; any other string is still refused. A single-recordGETanswering 200 withnullor an empty object is reported as not found.A rejected API key (401) produces a message that says which variable to fix and where the token comes from; a 403 names the roles WorkMobile's documentation lists for the API user; a 400 passes on WorkMobile's
title,detailand fielderrorswhen the body is ProblemDetails JSON. The API key is scrubbed from every error message, and contact details are redacted from WorkMobile's error text.
Tests
npm testThe spec (spec.json, from https://www.esayworkmobile.co.uk/webapi2/swagger/v2/swagger.json) and the connector definition (connector.json, from microsoft/PowerPlatformConnectors, certified-connectors/WorkMobile/apiDefinition.swagger.json, publisher eSAY Solutions Ltd) are downloaded on the first run if they are missing. Both are gitignored.
Where the schemas come from:
Request bodies: the v2 spec's own component schemas (
JobSearchCriteria,CompletedRecordsSearchCriteria,ApproveRequest,RejectRequest, all withadditionalProperties: false) and its multipart schema forPOST /api/jobs. One substitution: the spec'sUserDefinedFieldFilteris an object with no properties andadditionalProperties: false, so it accepts only{}and would reject every documented filter example; filter items are validated against the spec'sUserDefinedFieldFilterDefinition(uniqueName,operator,value, …) instead, and record search bodies without filters are validated against the unmodified schema.Responses of forms, job types, jobs, mobile users and user groups: the connector definition's response schemas. The help centre's documented
GET /api/Formsexample shows"LastUpload": nullwhere the connector types a string, so that one field is allowed to be null.Job search rows: WorkMobile documents the response only as "the list of jobs that meets the criteria". The mock answers a bare JSON array of objects shaped like the connector's
GET /api/Jobs/{id}schema; this is an assumption.Completed records: WorkMobile publishes neither a schema nor an example.
test/schemas.mjsholds a schema written by hand from what is documented (the numericId,OriginalIdandJobIdcolumns the search documentation filters and sorts on, and theCreatedandCreatedBystatic fields of the documented form example), with every other key taken to be one of the form's fields by unique name. The whole row shape, and the bare-array envelope, are assumptions.Job type schema document: the help centre's sample, copied verbatim (
test/doc-examples.mjs) and served as job type 10238; the two documentedGET /api/Formsexamples are served verbatim too.Job status list, approval inbox totals, error bodies, write replies: not documented; the mock's placeholders are listed at the top of
test/mock-server.mjs. The tools that return the first two pass them through without relying on any field name.
The test suite (28 checks):
Validates every fixture against those schemas, reports any fixture key the connector schema does not declare, confirms the documented examples are served unchanged, and runs negative controls (a string
JobId, a record withoutOriginalId, an undeclared key). Checks the field-name classifier on its own against personal, financial and ordinary names in PascalCase, acronym, dotted and spaced forms, and the bank-detail text patterns.Starts a local mock of the API under
/webapi2that requires theX-API-Keyheader (401 "Unauthorised" without it or with a wrong key), serves 1,100 jobs and 620 records on one form in pages of 500 with the documentedpaginationbody (refusing arowCountbelow 500, and a record search withoutorderBy, which the help centre says is always required), answers unknown IDs with a 404 and bad requests with a 400 as ProblemDetails, and answers the firstGET /api/formswith a 429. The mock's list and detail responses are validated against the schemas above.Starts the built server and drives it over stdio with the official MCP client: tools/list and annotations; every read tool; job search pagination across three pages to the short last page, continuation from
next_page, and the note when rows of a fetched page are not returned; every documented job search filter and the record search criteria (created and uploaded dates, job, mobile user, user group, field filters with AND/OR, sort order) sent exactly as documented, in bodies validated against the spec; redaction by default (inlist_jobs,get_job,list_form_submissions,get_form_submission,list_approvalswith aform_id,list_mobile_users,list_formsand the approval inbox totals: personal fields by name, includingDoB,D.O.B,NINumber,What3WordsandMob No, a bare link, emails, phone numbers, postcodes and coordinates in text) and its return on request (inget_job,get_form_submission,list_approvalsandlist_mobile_users), with bank and card fields, a Luhn-valid card number, a sort code, an account number and an IBAN typed into text, and an embedded image withheld even on request; object- and array-valued system columns and an objectLocationredacted like job data; a double-encoded job type schema (read byget_job_typeand used bycreate_job) and job search answer decoded; the write tools absent withWORKMOBILE_ALLOW_WRITESunset and set tofalse;create_jobposting a multipart form whose fields are all in the spec's schema and validate against it, withDataas{"jobData": …}, and refusing unknown fields, missing required fields, disallowed values, text for a numeric field and half a location before anyPOST;allocate_job,approve_recordandreject_recordbodies validated against the spec, and a 400 passed on; invalid IDs, dates (including month 13, 30 February and hour 24, with the validation message checked) and operators refused with no request made; the 404 message for unknown job and job type IDs and a 200nullor{}job read as not found; requests of a paged search spaced by the 250 ms throttle, and paging stopping oncemax_resultsrows are in hand; the 429 retry waiting forRetry-Afterin the seconds, fractional-seconds and HTTP-date forms and the 2 s fallback when the header is absent, retrying aPOSTwith the same body (page 2 of a three-page search asked for again with no row lost or duplicated, and a rate-limitedPOST /api/jobscreating exactly one job), giving up after three attempts on a persistent 429 and at once on aRetry-Afterabove the cap; a 502 retried for aGETand never for aPOSTsearch or write, with the right advice in each case; three 503s on aGETreported without HTML; a non-JSON 200, a search answer that is not an array, and a 403 (whose body echoing the API key and an email address reaches the tool result scrubbed); the 401 message for a wrong key, after exactly one request; and, last, that every request carriedX-API-Key(the configured one, except the deliberate wrong-key call) and noAuthorizationheader, matched exactly one method and path documented in the spec (integer path parameters match digits only, so a parameter cannot stand in for a literal segment), sent a body content type the spec accepts for that operation, and never touched a login, download, media or report endpoint.
The suite takes about 35 seconds.
Status
This is a working prototype. It has not been run against the live API, because it was built without a WorkMobile account (trials are set up through WorkMobile's sales team). Everything below comes from the published documentation and should be confirmed on a real account:
The response of
POST /api/jobs/searchandPOST /api/forms/{FormId}/completedrecords/search: that each page is a bare JSON array (not an object with the rows and a total, and not the JSON string the connector definition types the job search answer as; a double-encoded array is decoded, but that path has only been tried against the mock), that job rows carry the same fields asGET /api/jobs/{id}, and that a page with fewer than 500 rows is the last. If the envelope differs, the server says so with the key names it got rather than guessing.The shape of a completed record: that
Id,OriginalIdandJobIdare columns with those names, which other system columns exist and what they are called, and that the form's fields are keyed by unique name with scalar values. Also how photos, signatures, sketches and locations appear in a record (a link, an ID, embedded data), so the name- and value-based withholding can be checked against real data.Whether the record search criteria are sent as arrays (
createdDateFrom: ["…"],id: [21524]), as the current spec types them, or as the single values the 2021 help centre article shows; and howuserDefinedFilteritems are really read, given the spec's emptyUserDefinedFieldFilterschema.Which time zone WorkMobile applies to the search dates. The server sends UTC (
…Z), as the documented examples do; the timestamps in the documented responses carry no zone.Whether
GET /api/jobtypes/{id}/schemareturns the JSON Schema document as an object (as the help centre sample shows) or as a JSON string holding it (as the connector types it); both are handled, only against the mock. And whether a job'sLocationis ever an object rather than the connector's string.What the API answers for an unknown job, job type or form ID (the spec lists only 200 and 401 for these GETs); the server handles a 404 and a 200 with
nullor{}.The format of
GET /api/jobs/jobstatuslistandGET /api/approvals/getinboxtotals(returned as sent, with text redaction), and the IDs of the nine documented statuses.The error bodies: whether 400s come as ProblemDetails, and whether 401 and 403 carry anything. What a 403 looks like for a portal user that lacks a role.
POST /api/jobs: thatDatais the JSON document{"jobData": {…}}(the documented JSON Schema describes an object with ajobDataproperty), thatAllocatedMobileUserIdandAllocatedUserGroupIdof0mean unallocated (as the connector says), whatPriorityvalues andDurationunit are valid, thatEstimatedStartaccepts a UTC date-time, and that the reply is the new job ID as an integer (as the connector types it). The local check of job data covers only unknown fields, required fields (marked, as in the documented sample, by arequiredarray holding the field's own name), allowed values and numeric types; WorkMobile's own validation is the authority.The replies of
allocate(typed as a string by the connector),approveandreject(documented only as "Success"), and whatapprove/rejectanswer for a record with no step pending for the API user.Whether a 429 on a
POSTmeans the request was not processed (the retry assumes so), and how many requests per second the API tolerates; nothing is documented, so the 250 ms spacing is a guess on the polite side.The roles the API user needs for each tool; the 403 message quotes role names from the example token in WorkMobile's authentication article.
Going to production
This version runs locally over stdio, with the account holder's own API token. For customers to connect from claude.ai or ChatGPT without handling tokens, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by eSAY Solutions, and then a listing in the Claude and ChatGPT connector directories. A production version would also cover what this one leaves out for lack of documented response shapes (CRM customers and contacts, lone worker events, record history), once those shapes are confirmed.
Licence
MIT. Built by Alexandru Dragoș (alexandru.dragos96@gmail.com) with an AI agent (Claude) working under his direction.
This server cannot be deployed
Maintenance
Related MCP Connectors
Give AI assistants secure access to your organization's structured business data. Search records, create and update records, retrieve schema information, and manage workflow states using natural language. You need two values for every request: x-api-key — your Web Data Forms API Key x-group-id — your Web Data Forms Group ID You can find these in your Web Data Forms accounts group->information page. Preferred method: request header When possible, pass the credentials as HTTP headers: x-api-key: <your-api-key> x-group-id: <your-group-id> This is the preferred option because it keeps credentials out of the URL and is more secure. Fallback method: query parameters If your MCP client does not support custom headers, the server also accepts the credentials as URL query parameters. Example: https://mcp.webdataforms.com?x-api-key=abc123&x-group-id=xyz456 Detailed information here: https://github.com/Web-Data-Forms/mcp-server-docs/blob/main/README.md
Let AI agents query data and act across all your business apps via MCP.
MCP server that lets AI assistants use all OneSchema features exposed via the public API.
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to read and manage a Jobkeepr field service business including jobs, customers, scheduling, estimates, invoices, and payments via MCP.MIT
- AlicenseAqualityCmaintenanceEnables Claude, ChatGPT and other MCP clients to read an Amiqus ID account—clients, onboarding records and steps, check results, templates, case status counts and webhooks—and, when writes are enabled, create records.9MIT
- AlicenseNot gradedqualityCmaintenanceEnables Claude, ChatGPT and other MCP clients to read practice-management data including organization, clinicians, diaries, availability, bookings, patients, invoices, payments, staff tasks, services, and locations, and optionally create staff tasks, create bookings, and cancel bookings.MIT
- AlicenseNot gradedqualityCmaintenanceLets MCP clients such as Claude and ChatGPT read a rota and time-and-attendance account, exposing venues, groups, shifts, absences and absence types, time entries, venue events and staff names through read-only tools that never return pay data.MIT