Rotor MCP
Connect Rotor via MCP to analyze your data!
1. What it is
Rotor is a CRM and field-service platform (leads, customers, jobs, visits, tasks, messaging,
quotes, invoices, payments, payroll, timesheets, and map-pin canvassing). The Rotor MCP server exposes a Rotor company's own records to Claude over Streamable HTTP, authenticated with OAuth 2.1 against the user's existing Rotor account.
Every tool runs as the connecting Rotor user, inside that user's company, with that user's
role, feature access, and record visibility. There is no admin backdoor and no cross-company
read.
2. Connection details
| Field | Value |
|---|---|
| Server URL | https://mcp.getrotor.com/mcp |
| Transport | Streamable HTTP |
| Same URL for all users | Yes |
| Authentication | OAuth 2.1, authorization code + PKCE (S256), dynamic client registration |
| Issuer | https://mcp.getrotor.com |
| Protected-resource metadata | https://mcp.getrotor.com/.well-known/oauth-protected-resource/mcp |
| Authorization-server metadata | https://mcp.getrotor.com/.well-known/oauth-authorization-server |
| Prerequisite | An active Rotor account with membership in at least one company |
| Data direction | Reads and writes |
Claude also POSTs Streamable HTTP to the issuer origin (/); the server accepts both / and /mcp with identical middleware (server.ts:90-94).
3. Authentication
Standard OAuth 2.1 discovery, registration, authorization, token. Implemented in
mcp.oauth.ts.
- Dynamic client registration at
POST /oauth/register.token_endpoint_auth_methodis
none(public client). - PKCE is mandatory.
code_challenge_methodmust beS256; anything else is rejected
withinvalid_request(mcp.oauth.ts:282). - Consent is a Rotor-hosted screen.
/oauth/authorizeredirects to the Rotor web app,
which authenticates the user, lets them pick which company to connect when they belong to
more than one, and itemizes every requested scope in plain language before they approve. - Access tokens are HS256 JWTs, 1 hour TTL, signed with a dedicated
MCP_JWT_SECRET
(never the client-bundledROTOR_API_KEY), scoped byaudto the MCP resource URL. - Refresh tokens are opaque, stored only as SHA-256 hashes, 30-day TTL, and rotated on
every use - the previous token is markedrotated_atin the same conditional update, so a
replayed refresh token fails. - Revocation at
POST /oauth/revoke, and from Rotor itself: Company Settings, Connectors
lists every active grant and revokes it (DELETE /api/mcp-management/connectors/:grantId).
Revoking a grant kills its access tokens immediately - token verification re-checks the grant
and the live company membership on every request. - Authorization codes are single-use with a 10-minute TTL.
Scopes
25 scopes are advertised and requested as one set. The consent screen is where the user sees
and controls them.
| Scope | Grants |
|---|---|
contacts:read | List visible leads and customers |
members:read | Read company member names, emails, roles |
leads:read / leads:write | Read / create and update leads |
customers:read / customers:write | Read / create and update customers |
jobs:read | Read visible jobs |
visits:read / visits:write | Read / reschedule visits |
tasks:read / tasks:write | Read / create and update tasks |
inbox:read | Read visible inbox messages |
campaigns:read / campaigns:write | Read campaigns / create them and queue recipients |
templates:read / templates:write | Read / create message templates |
service_plans:read | Read visible service plans |
pins:read | Read map-pin and canvassing activity |
invoices:read | Read visible invoices |
quotes:read | Read visible quotes |
payments:read | Read company payments |
payroll:read | Read payroll records and rate settings - admins only |
pipelines:read | Read pipelines and stages |
timesheets:read | Read timesheets and clock status |
offline_access | Stay connected until the user revokes |
payroll:read is dropped from the grant for non-admin members at consent time
(scopesForMembership, mcp.oauth.ts:396), so a member's token cannot carry it.
4. Tools
47 tools: 37 read-only, 10 write. Every tool declares a title and the applicable hint.
Read and write are separate tools throughout - there is no method-parameterized catch-all, and
no tool accepts a freeform endpoint, path, or request body.
destructiveHint follows the MCP schema meaning (destructive vs. additive), not "is a write":
a tool that only inserts a new row declares false; one that overwrites existing field values
declares true. mcp.server.ts enforces this at the type level - a write tool cannot omit the
hint.
Read-only tools (readOnlyHint: true)
readOnlyHint: true)| Tool | Scope | What it does |
|---|---|---|
get_current_context | - | Return the Rotor user, company, role, and scopes for this connection. |
list_company_members | members:read | Active company members with the user UUIDs other tools accept. |
list_contacts | contacts:read | Search leads and customers together by name, email, phone, address. |
list_leads | leads:read | List visible leads, filterable by search text and stage. |
get_lead | leads:read | One lead by ID. |
list_customers | customers:read | List visible customers, filterable by search text and status. |
get_customer | customers:read | One customer by ID. |
list_jobs | jobs:read | List visible jobs, filterable by search text and status. |
get_job | jobs:read | One job by ID. |
list_visits | visits:read | Scheduled visits in an ISO date range, with job, customer, service address. |
get_visit | visits:read | One visit by ID. |
get_clock_status | timesheets:read | Current user's active clock status and clock-in categories. |
list_timesheets | timesheets:read | Timesheet entries in a range. Members see their own; admins see company-wide. |
get_timesheet | timesheets:read | One timesheet entry by ID. |
list_visit_timers | visits:read | Visit timer segments in a range, same member/admin split. |
get_visit_timers | visits:read | All timer segments for one visit. |
list_tasks | tasks:read | List visible tasks, filterable by search, status, priority. |
get_task | tasks:read | One task by ID. |
list_messages | inbox:read | Inbox messages newest-first, filterable by contact, channel, direction, date. |
list_campaigns | campaigns:read | Campaigns newest-first, when the campaigns feature is enabled. |
list_message_templates | templates:read | Company and Rotor example email/SMS templates, including bodies. |
list_quotes | quotes:read | Quotes with statuses, totals, contacts, timestamps. |
get_quote | quotes:read | One quote with its current status and details. |
list_payments | payments:read | Payment records and amounts, filterable by method, type, status, date. |
get_payment | payments:read | One payment record by ID. |
list_payroll | payroll:read | Paid payroll lines for a range; optional hourly source lines and rate tables. Admins only. |
list_pipelines | pipelines:read | Pipelines visible to the user. |
get_pipeline | pipelines:read | One pipeline and its ordered stages. |
list_pipeline_stages | pipelines:read | Ordered stages across visible pipelines. |
get_pipeline_stage | pipelines:read | One stage by ID. |
list_service_plans | service_plans:read | Visible service plans, filterable by active state. |
get_service_plan | service_plans:read | One service plan by ID. |
search_map_pins | pins:read | Search map-pin / door-knocking activity: coordinates, notes, type, assignees, timestamps. |
list_map_pin_types | pins:read | The company's pin categories (Go-Back, Contacted, Sold, custom). |
summarize_map_pin_activity | pins:read | Aggregate pin activity by day, team member, and pin type. |
list_invoices | invoices:read | Visible invoices, filterable by search text and status. |
get_invoice | invoices:read | One invoice by ID. |
Write tools (readOnlyHint: false)
readOnlyHint: false)| Tool | Scope | destructiveHint | What it does |
|---|---|---|---|
create_lead | leads:write | true | Creates a lead. Emits record-created, plus tag-added and stage-change events; configured automations may then send email or SMS or change other records. No dedupe against existing contacts. |
update_lead | leads:write | true | Updates a lead. Every update emits record-updated; stage and tag changes emit more. |
create_customer | customers:write | true | Creates a customer. Emits record-created and, with tags, tag-added; automations may follow. |
update_customer | customers:write | true | Updates a customer. Status and tag changes emit automation events. |
update_visit | visits:write | true | Reschedules a visit. Rescheduling a job's first visit also moves the job start date; emits job- and visit-rescheduled events. |
update_task | tasks:write | true | Updates a task, its assignees, or its contact link. Runs no automations, sends no notifications. |
create_task | tasks:write | false | Inserts a task with assignee and contact links. Runs no automations, sends no notifications. |
create_campaign | campaigns:write | false | Creates an inactive campaign from a template. Queues no recipients and sends nothing. |
add_people_to_campaign | campaigns:write | false | Queues up to 100 visible leads/customers as pending recipients, skipping duplicates. Sends nothing by itself. |
create_message_template | templates:write | false | Stores a reusable email or SMS template. Sends nothing. |
No tool sends a message, charges a card, moves money, deletes a record, or generates AI
media. The three write tools that touch messaging (create_campaign,
add_people_to_campaign, create_message_template) all stop short of sending; a send is a deliberate action a human takes in the Rotor app. The tools whose side effects can reach a customer do so only through the company's own configured automations, and each says so in its description.
5. Access control
Four independent gates, all server-side:
- Company scoping. Every query filters on the token's
company_id. The token binds one
user to one company; connecting a second company is a second authorization. - Role and record visibility. Non-admin members see only records assigned to them, matching
what the Rotor web and mobile apps show them.list_payrollis admin-only. - Feature access.
requireFeatureAccessresolves the company feature gate and the
member's ownfeature_overrides/default_feature_access, the same resolver the apps use -
a per-member deny is honored, not just the company setting. - Scope. Each tool declares its required scope in
MCP_TOOL_SCOPES; a missing scope fails
withinsufficient_scopeand names the scope in the error.
Membership is re-validated on every token verification, so removing someone from a company ends
their MCP access without waiting for token expiry.
6. Errors, limits, and response size
- Errors are structured, never bare 500s:
{ code, message, retryable, hint?, example_args?, missing_scope?, allowed_values? }(mcp.errors.ts). Internal messages are not leaked to the
model. - Inputs are validated with Zod. IDs must be well-formed UUIDs or positive integers; date ranges
require ISO 8601 with a timezone offset. - Pagination: default 25, maximum 100 per page. Visit ranges are capped at 62 days. No tool
returns an unbounded dump. - Rate limits: 60-second windows, per-user for tools (keyed on the authenticated Rotor user, not
IP, so one Claude customer cannot throttle another) and per-IP for the OAuth endpoints. - Origin validation: browser requests must present an allowed
Origin; the wildcard CORS header
is stripped from every MCP response.
7. Privacy and data handling
- First-party. The server calls Rotor's own API and database. Nothing is proxied from a third
party. - What it accesses: only the connected Rotor company's records, scoped to the connecting
user. Rotor CRM data includes customer names, contact details, service addresses, and billing
records - all of it already the customer's own data in their own Rotor account. - What it does not do: it does not read Claude's memory, chat history, conversation
summaries, or user files, and it collects no conversation data beyond the tool arguments needed
to serve the call. - No health data. No sponsored content. No financial transfers.
- Privacy policy: https://getrotor.com/privacy | Terms: https://getrotor.com/terms
8. Example prompts
- "What visits are on the schedule tomorrow, and for which customers?"
- "Which leads came in this week and what stage is each one at?"
- "Summarize our door-knocking numbers for last week - who dropped the most pins, and what was the Go-Back rate?"
- "Find the pins from Saturday that have phone numbers in the notes and create leads for them."
- "Show me unpaid invoices over $2,000."
- "Move Thursday's 9am visit for the Ramirez job to Friday at 8am."
- "Create a follow-up task for me on the Chen quote, due Monday."
9. Submission checklist
| Requirement | Status |
|---|---|
Remote server, https://, Streamable HTTP | Met - https://mcp.getrotor.com/mcp |
| OAuth 2.0/2.1 for authenticated service | Met - DCR + authorization code + S256 PKCE |
Every tool has a title | Met - 47/47 |
Every tool has readOnlyHint or destructiveHint | Met - type-enforced |
| Reads and writes in separate tools | Met - no method-parameterized tool |
| No freeform-endpoint query tool | Met - none exist |
| Tool names 64 chars or fewer | Met - longest is summarize_map_pin_activity (26) |
| Descriptions match behavior, no prompt-injection patterns | Met - descriptive only; no instructions about Claude's behavior |
| Actionable errors, no bare 500s | Met - structured McpHttpError |
| Bounded responses | Met - page max 100, 62-day visit cap |
| First-party API, domain matches service | Met - mcp.getrotor.com |
| No money transfer, no AI media generation | Met |
| Privacy policy URL | Met - https://getrotor.com/privacy |
| Public documentation URL | Outstanding - publish this document at a public URL before the listing goes live |
| Test account, fully populated | Outstanding - provision and record credentials in the portal |
| Every tool exercised via Inspector or custom connector | Outstanding - run before submitting |
| Allowed link URIs | n/a - the server does not use ui/open-link |
| Carousel screenshots | n/a - not an MCP App (no UI resources) |
Updated about 1 hour ago