synchroteam-mcp
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
synchroteam-mcp
An open-source Model Context Protocol server that lets Claude (or any MCP client) work with Synchroteam field-service data: jobs, customers, sites, equipment, technicians and their schedules, invoices, quotations, parts and stock.
Status: unofficial, pre-release.
Not affiliated with, endorsed by or supported by Synchroteam.
Built from the public Synchroteam REST API v3 documentation (read 2026-11-08; links at the end).
Tested only against a local mock of that API (
httpx.MockTransport, 157 tests).Not yet tested against a live Synchroteam account. Expect small mismatches where the docs are ambiguous (listed under Known gaps) until someone runs it against a real tenant.
What you get
21 read-only tools (19 lookups + 2 composite "business" tools), on by default.
5 write tools (create job, add note, schedule, complete, cancel). They are not registered at all unless
SYNCHROTEAM_ALLOW_WRITE=true.Two transports from one codebase: stdio (Claude Desktop, Claude Code) and Streamable HTTP (remote clients).
Role-based access: a JSON file maps roles (e.g. engineer / sales / management) to tools. A tool outside the caller's role is not listed, and calling it is refused.
Bearer-token auth for HTTP: tokens map to roles, are stored as SHA-256 hashes, and a missing or unknown token gets
401.Audit log: one JSON line per tool call with role, tool, sanitised arguments, result size, duration and outcome. No secrets, and no full personal data.
Careful API client: documented Basic auth, pagination, timeouts, retries with backoff that read the documented
X-RateLimit-*/X-Quota-*headers, and clear error messages.
Built with the official MCP Python SDK 2.x. Its high-level
API, MCPServer, is what 1.x called FastMCP.
Related MCP server: fieldroutes_mcp
Tools
Endpoints are relative to https://ws.synchroteam.com/api/v3. "Roles" refers to config/roles.example.json
(management has *).
Tool | What it answers | Synchroteam endpoint(s) | Example roles |
| Jobs by status, date range, technician, team, customer, site, equipment, type |
| engineer, sales |
| One job in full: schedule, actual times, time spent, time entries, parts used, report summary |
| engineer |
| Customers by name (wildcard) or email (exact) |
| sales |
| One customer: contact, VAT number, custom fields |
| engineer, sales |
| Customer sites (service locations) |
| engineer, sales |
| Installed equipment for a customer or site |
| engineer, sales |
| One machine plus its most recent jobs (quick service history) |
| engineer |
| Technicians, managers, admins: login, teams, skilled trades |
| management |
| Teams with technician/manager counts |
| management |
| One technician's jobs and activities (holiday, training, travel) with a per-day summary, up to 31 days |
| engineer |
| Invoices by status/customer/job, and one invoice with line items |
| sales |
| Quotations by status/customer/job |
| sales |
| Parts and services catalogue with stock level and a below-minimum flag; optional text search |
| engineer, sales |
| One part by id or exact reference |
| engineer, sales |
| Depots and vans, and the parts held in one |
| engineer |
| Notes, calls, emails and status notifications for a job/customer/site/equipment |
| engineer, sales |
| Completed/validated jobs in a date range with no invoice linked |
| management |
| Account summary: sites, equipment, open and recent jobs, unpaid invoices, open quotes |
| sales |
| Create an unscheduled job for a customer |
| - |
| Add a timeline note to a job | ( | engineer |
| Assign technician and time slot |
| - |
| Mark completed with actual start/end |
| engineer |
| Cancel a job |
| - |
List tools return compact summaries plus page, total, has_more, next_page and a hint when more results
exist (page_size is capped at the documented maximum of 100). Every tool carries MCP annotations
(readOnlyHint, destructiveHint, ...), and every write tool says "WRITES TO SYNCHROTEAM" in its description.
The two business tools, and their limits
completed_jobs_not_invoiced(date_from, date_to, ...): the documented job object has no "invoiced" flag. The
tool lists completed (and, by default, validated) jobs in the range, then scans invoices created since
date_from - invoice_lookback_days (default 30), ignoring cancelled ones. It matches the two through each
invoice's job reference (id, num or myId). It returns the uninvoiced jobs together with caveats covering:
invoices created without a job link (it reports how many it saw);
quotations and credit notes, which are not considered;
truncation caps (
max_jobs, a 5000-invoice scan).
verify_each_job=true adds one invoice/list?job_num= lookup per candidate (at most 100). That also catches
invoices created outside the scan window.
customer_overview(customer_id | customer_my_id | customer_name): about 10 API calls. Invoice totals are
summed over at most 100 invoices per status. The API exposes no currency field, so amounts have no currency.
Invoices and quotations are queried once per status, because the docs do not say whether their status filter
accepts several values.
Setup
Requires Python 3.11 or newer.
git clone <this repo> synchroteam-mcp && cd synchroteam-mcp
python -m venv .venv
# Windows: .venv\Scripts\activate macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env # then fill in the placeholders, or set the variables in your MCP client config
synchroteam-mcp check-configWhere the credentials come from (according to the Synchroteam docs):
Domain: the
mycompanypart ofhttps://mycompany.synchroteam.com.API key: in the Synchroteam web app under Configuration > Authentication Key.
Configuration
Variable | Default | Meaning |
| (required) | Your Synchroteam domain identifier |
| (required) | API key (Configuration > Authentication Key) |
|
| API base. |
|
| Register the write tools |
|
| Per-request timeout (s) and retry budget |
| (none) | JSON roles file. Without it, a single role |
|
| Role used by the stdio transport (required when a roles file is set) |
| (none) | HTTP: JSON file of |
| (none) | HTTP: |
| (none) | Path of the JSON-lines audit log |
|
| HTTP bind address and endpoint |
| (none) | Comma-separated public hostnames. Turns on Host/Origin checking when serving behind a domain |
|
| Stateless Streamable HTTP with plain JSON responses (load-balancer friendly) |
|
| Log level (logs go to stderr) |
Use with Claude Desktop (stdio)
Edit claude_desktop_config.json (Settings > Developer > Edit Config):
{
"mcpServers": {
"synchroteam": {
"command": "C:\\path\\to\\synchroteam-mcp\\.venv\\Scripts\\synchroteam-mcp.exe",
"args": ["--transport", "stdio"],
"env": {
"SYNCHROTEAM_DOMAIN": "your-domain",
"SYNCHROTEAM_API_KEY": "your-api-key",
"SYNCHROTEAM_MCP_ROLES_FILE": "C:\\path\\to\\synchroteam-mcp\\config\\roles.example.json",
"SYNCHROTEAM_MCP_ROLE": "management",
"SYNCHROTEAM_MCP_AUDIT_LOG": "C:\\path\\to\\synchroteam-mcp\\logs\\audit.jsonl"
}
}
}
}On macOS/Linux, the command is /path/to/synchroteam-mcp/.venv/bin/synchroteam-mcp.
Use with Claude Code
# Local, stdio
claude mcp add synchroteam \
--env SYNCHROTEAM_DOMAIN=your-domain \
--env SYNCHROTEAM_API_KEY=your-api-key \
-- /path/to/.venv/bin/synchroteam-mcp --transport stdio
# Remote, Streamable HTTP with a bearer token
claude mcp add --transport http synchroteam https://mcp.example.com/mcp \
--header "Authorization: Bearer <token issued to you>"Remote deployment (Streamable HTTP)
synchroteam-mcp hash-token # prints a new random token (give it to the user) and a tokens-file entry
synchroteam-mcp --transport http # serves http://127.0.0.1:8000/mcp, plus GET /healthz without authDocker (non-root user, /healthz health check):
docker build -t synchroteam-mcp .
docker run --rm -p 8000:8000 --env-file .env \
-e SYNCHROTEAM_MCP_ROLES_FILE=/etc/synchroteam-mcp/roles.example.json \
-e SYNCHROTEAM_MCP_TOKENS_FILE=/run/secrets/tokens.json \
-v "$PWD/tokens.json:/run/secrets/tokens.json:ro" \
synchroteam-mcpThe Dockerfile has not been built in the development environment (Docker was not available there). Only the Python wheel it installs was built and checked.
Deployment notes:
Put TLS in front (reverse proxy, load balancer or platform ingress). The server speaks plain HTTP.
Set
SYNCHROTEAM_MCP_ALLOWED_HOSTS=mcp.example.comwhen serving on a public hostname. This turns on the SDK's Host/Origin validation; it is automatic only when bound to localhost.Stateless mode (the default) needs no sticky sessions, so you can run several replicas behind a load balancer.
One server instance uses one Synchroteam API key. Roles restrict which tools a caller can use, but every call runs with that key's permissions inside Synchroteam.
OAuth (what Claude.ai custom connectors prefer) is not implemented
This server authenticates HTTP callers with static bearer tokens. That works for Claude Code, scripts, the MCP
Inspector and other clients that can send an Authorization header. Claude.ai custom connectors are built around
the MCP authorization spec (OAuth 2.1), so this server is not yet a drop-in Claude.ai connector. Check
Anthropic's current connector documentation for the exact requirements.
Adding OAuth would take:
An authorization server (Microsoft Entra ID, Auth0, Keycloak, ...) that issues access tokens for this server as an audience/resource, ideally with dynamic client registration or a pre-registered client for Claude.
A
TokenVerifierimplementation. The MCP SDK provides the hook (MCPServer(token_verifier=..., auth=AuthSettings(...))). It would validate the JWT signature, issuer, audience and expiry, and map a group/role claim onto the roles file.Publishing Protected Resource Metadata (RFC 9728). The SDK serves it from
AuthSettings.Replacing
BearerAuthMiddlewarewith the SDK's auth middleware. The role and audit layers stay unchanged.
Try it without a Synchroteam account (fake API + MCP Inspector)
python scripts/fake_synchroteam.py # fake API on http://127.0.0.1:8765/api/v3 with fictional data
# second shell (bash syntax; use `set`/`$env:` on Windows)
export SYNCHROTEAM_BASE_URL=http://127.0.0.1:8765/api/v3
export SYNCHROTEAM_DOMAIN=acme-test
export SYNCHROTEAM_API_KEY=00000000-test-key-not-real-0000 # placeholder accepted only by the fake
npx @modelcontextprotocol/inspector .venv/bin/synchroteam-mcp --transport stdioThe Inspector opens a browser UI where you can list tools and call them. To inspect the HTTP transport, start
synchroteam-mcp --transport http with SYNCHROTEAM_MCP_TOKENS=full:<a 24+ character token>. Then choose
"Streamable HTTP" in the Inspector, use http://127.0.0.1:8000/mcp and add an Authorization: Bearer ... header.
Security model
Read-only by default. Write tools are only registered when
SYNCHROTEAM_ALLOW_WRITE=true. When it is off, they are absent fromtools/listand cannot be called by any role.create_jobrefuses to reuse an existingmyId, because Synchroteam'sjob/sendwould otherwise silently update that job (the documented upsert).Roles. Enforced in an MCP server middleware for every transport. The role filters
tools/listand refusestools/callwith a protocol error before the API is touched. The check fails closed: an HTTP request without an authenticated principal sees no tools. Tool names in the roles file are validated at startup, so a typo does not silently hide a tool.HTTP authentication.
Authorization: Beareris checked in plain ASGI middleware before the MCP SDK runs. Tokens are compared as SHA-256 digests withhmac.compare_digest. The tokens file holds only hashes and is rejected if it contains plaintext.tools/listresults are marked cache scopeprivate.Audit. One JSON line per call:
{"ts":"2026-11-08T17:20:01.123Z","event":"tool_call","transport":"http","role":"sales","principal":"sales-team", "tool":"get_customer","arg_keys":["customer_email"],"args":{"customer_email":"<redacted:28>"}, "outcome":"ok","error":null,"result_bytes":1834,"duration_ms":212.4}Argument values are kept only for ids, enums, dates and paging; names, emails and free text become length markers.
Results are never written, only their size.
Refused calls are logged with
outcome: "denied".
Secrets.
The API key and tokens come only from the environment or files outside git (
.env,tokens.jsonandconfig/roles.jsonare git-ignored).They are excluded from
repr().The base URL must be
https://unless it points at localhost.
Log hygiene.
All logging goes through a redacting filter that masks emails and phone numbers and strips Basic/Bearer credentials and the API key.
httpxrequest logging (which would print query strings such as email filters) is turned down to WARNING.
Data minimisation.
User
passwordfields are always stripped. The docs show the field in the user object example, although they call it write-only.Technician GPS (
lastLocation,startLocation) is not returned.
Error messages tell the model what to fix ("No job found with num=99999", "rate limit reached", "check SYNCHROTEAM_API_KEY") without echoing credentials.
Known gaps and doc ambiguities
Not verified against a live account. The points below come from reading the docs, and each is handled in the code as described:
Base path. Resource examples use
/api/v3/..., the auth example/Api/v3/, and the pagination/error examples/v3/job/list. The default is/api/v3; override it withSYNCHROTEAM_BASE_URL.Rate limits. The text says 1000 requests/minute, while the error example shows
X-RateLimit-Limit: 120. The daily quota appears as 15000 in one example and 10000 in another. The client reads the headers instead of assuming a number. It treats a 429 withX-Quota-Remaining: 0as the daily quota (not retried, resets at UTC midnight), inferred from the docs' example.Retry-Afteris not documented but is honoured if present.Date formats.
Job object examples use
19/07/2016 14:43:00(day/month/year); other objects use2018-04-06 10:19. Whether this depends on account locale is not stated. The server parses both and always returns the original string.Job
dateFrom/dateTofilters do not state a time zone.changedSince-style filters are documented as UTC, and activities as local time.
customer/list. The parameter table sayschangedSince, but the example URL useschangedFrom. The server useschangedSince.activity/list.user_loginis typed "integer", apparently a typo. The server filters byuser_id.The category filter lists
unavailability, while the category code isunavail. The server filters by category client-side and accepts both.
Job reports. The block title is
namein one example andblockNamein another. Both are read. The report embedded injob/detailsis used; if it is missing,get_jobsays so rather than guessing.job_idfilters typed as integer oninvoice/list,quotation/listandtimeline/list, although job ids are strings such as265_160719144323649. The invoice and quotation tools exposejob_num/job_my_idinstead.list_timelinepassesjob_idas given, which is untested live.Multi-value
status. It is documented forjob/listbut not for invoices or quotations, so those are queried one status at a time.Depot ids. Typed int, but the example is a GUID. They are treated as strings.
Timeline.
Timeline (and tasks) lists are documented as bare arrays with no pagination parameters.
list_timelinecaps output client-side.The timeline create example uses
clientfor the customer while the object usescustomer.Whether
useris required ontimeline/sendis not stated;add_job_notesends only the job reference (by id, resolving num/myId first because the example only shows id).
pageSizeabove 100. The behaviour is undocumented, so the client never sends more than 100.Missing data. The job object has no invoice flag, invoices carry no currency, and there is no working-hours endpoint.
get_technician_scheduletherefore reports booked jobs and unavailability, not computed free slots.API access by plan. The Synchroteam help-centre article on web services (https://support.synchroteam.com/hc/en-us/articles/360015843091-Web-services) returned a Cloudflare bot check (HTTP 403) and was not read. Which subscription plans include API access is therefore unconfirmed.
Error bodies. They are assumed to follow the documented
{"error", "message"}shape. Other shapes are tolerated and reported with the HTTP status.
Before testing against a live Synchroteam account
Use a Synchroteam account with API access, ideally a trial or sandbox tenant rather than production. Confirm with Synchroteam that your plan includes web services.
Copy the domain and the key from Configuration > Authentication Key.
Start read-only (the default) and run
synchroteam-mcp check-config. Then try small calls:list_teams,list_jobswithpage_size: 1, thenget_job.Check the items listed above, especially the date formats in responses, the rate-limit headers, the
timeline/listjob_idfilter, thejobfield on invoices (whichcompleted_jobs_not_invoicedrelies on) and whether the job report is embedded injob/details.Enable
SYNCHROTEAM_ALLOW_WRITE=trueonly on a test tenant, and try each write tool once.
Development
pip install -e ".[dev]"
pytest # 157 tests; no external network (end-to-end tests use localhost only); ~10 ssrc/synchroteam_mcp/client.py: HTTP client (auth, pagination, retries, error mapping)src/synchroteam_mcp/tools/:read.py,business.py,write.pysrc/synchroteam_mcp/middleware.py: bearer auth (ASGI), role filter and audit (MCP middleware)src/synchroteam_mcp/testing/fake_api.py: fake of the documented API used by the tests and the demo scripttests/:client behaviour (auth header, pagination, 429/5xx retry and backoff, error mapping)
every tool over an in-memory MCP session
write-flag behaviour
role filtering
HTTP bearer checks via the SDK client
audit and log redaction
end-to-end runs of the real CLI over stdio and HTTP
Documentation used
Synchroteam REST API v3: https://api.synchroteam.com (main reference)
Synchroteam API v2 (legacy, consulted for comparison only; not used): https://api.synchroteam.com/v2/
Synchroteam web services help article (could not be retrieved, see above): https://support.synchroteam.com/hc/en-us/articles/360015843091-Web-services
MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk, migration notes for 2.x: https://py.sdk.modelcontextprotocol.io/v2/migration/
License
MIT, see LICENSE. "Synchroteam" is a trademark of its owner and is used here only to describe compatibility.
This server cannot be deployed
Maintenance
Related MCP Connectors
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
MCP server for Arcoro and ExakTime — workforce data, jobs, cost codes and locations.
184 MCP tools for field-service CRM, scheduling and double-entry accounting. Hosted; BYO agent.
Host any HTTP API as a standard MCP server; auto-distribute to 12+ mainstream agent platforms.
Related MCP Servers
- AlicenseAqualityBmaintenanceSelf-hosted, open-source (AGPL-3.0) MCP gateway that turns REST, SOAP/WSDL, GraphQL, and SQL/NoSQL databases into MCP tools. Imports OpenAPI/Postman/WSDL/GraphQL specs and bridges multiple MCP servers behind one endpoint. Ships 188 pre-built adapters, 26 of which need no API key, plus per-tool response mapping, OAuth2/RBAC/SSO and a full audit log.4756AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceMCP server for the FieldRoutes pest-control / lawn-care operations platform — talk to your data from Claude, Cursor, or any MCP client.MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server that gives agents safe, policy-scoped access to your database via structured queries (never raw SQL), with read and opt-in write support.12 npm5MIT
- FlicenseAqualityCmaintenanceEnables users to connect to Salesforce and perform CRUD operations, run SOQL/SOSL queries, and manage records through a lightweight MCP server with built-in safety gates for writes.91-