sheepCRM 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., "@sheepCRM MCP ServerWhen does Sam Evans's membership end and is it auto-renewing?"
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.
sheepCRM MCP server
An MCP server that lets Claude, ChatGPT and other MCP clients work with a sheepCRM membership database: search people and organisations, read a contact's summary and membership history, list events with their ticket types and attendance lists, read invoices, find and count segments, and (when enabled) record who attended an event. It is built from sheepCRM's public documentation only: the OpenAPI spec at https://sls-api.sheepcrm.com/.well-known/openapi.yaml (which the vendor describes as a work in progress; its source repository tracks 43 of about 180 endpoints as documented) and, for authentication and throttling, the legacy docs at https://docs.sheepcrm.com/.
Once it's connected, a membership secretary can ask things like:
"When does Sam Evans's membership end, and is it set to auto-renew?"
"Which events are coming up, and how many tickets are left for the AGM?"
"Who attended the AGM, and who was invited but didn't reply?"
"How many people are in the 'Members lapsing this month' segment?"
"Show me invoice INV-0001: what's been paid and what's still due?"
With writes enabled: "Mark Priya Shah as attended at the AGM."
Tools
Tool | What it does | API calls |
| sheepCRM's global search for a name or other text. Returns each match's URI, record type and display name; by default only |
|
| The exact-details person matcher, with every documented parameter passed through under its documented name ( |
|
| The summary profile of one person or organisation by URI. Response shape not documented; passed through with personal fields withheld. |
|
| A contact's full membership history: number, plan, status, start and end dates, days until the end date (computed), renewal and auto-renew flags, lapse reason, fee and whether it is paid. Latest end date first. Accepts a person or an organisation URI; the organisation form is outside the spec (see Status). |
|
| One member record by uid. Response shape not documented; passed through with personal fields withheld. |
|
| Events with dates, location, status, capacity and tickets left: the default window (past 14 days, next 90) or one documented |
|
| One event's details and its ticket types with price and availability, or the reduced summary with |
|
| An event's attendance list with each person's status, ticket and guest flag, and counts per status. The |
|
| One invoice, order or membership invoice: status, date, totals, paid and due, line items, the buyer's name. |
|
| Saved lists (segments): the active ones, all of them including inactive, or the active ones matching a name. Pages with |
|
| How many records a segment holds, with its description. |
|
| Sets one person's attendance status at an event ( |
|
Not covered on purpose: the /internal/* endpoints, segment creation, segment bulk actions (/action/replace, /action/delete), Mailchimp sync, exports and PDF/Excel reports, avatars and images, deleting event orders, rebasing tickets and questions, form responses, the personal and communications detail endpoints (their only purpose is contact data), and the audit log. There are no list_invoices or list_groups tools. The OpenAPI spec has no endpoint that lists invoices or groups. The legacy docs do name some: GET /api/v2/{bucket}/group/, /group/all and /group/{uid}/members/all (the last with an expansions parameter, open_tasks, and a status filter), and a contact's invoices at GET /api/v2/{bucket}/{organisation|person}/{uid}/invoices/summary and .../detail. They document no response shape for any of them, so they were left out rather than built on guesses.
Setup
Requires Node 18 or later.
npm install
npm run buildYou need two things from sheepCRM:
Your flock (the spec calls it the bucket): the name of your database, the first part of every sheepCRM URI, e.g.
example-associationin/example-association/person/6305f074683e800f3abe809e/.An API key. The legacy docs say you can set your own by logging into sheepCRM and going to your Profile settings. The server sends it as
Authorization: Bearer <key>, as those docs show. They also say a 403 means a problem with the key or with your permissions.
Claude Desktop: add this to claude_desktop_config.json:
{
"mcpServers": {
"sheepcrm": {
"command": "node",
"args": ["/absolute/path/to/sheepcrm-mcp/dist/index.js"],
"env": { "SHEEPCRM_FLOCK": "your-flock", "SHEEPCRM_API_KEY": "your-key" }
}
}
}Claude Code:
claude mcp add sheepcrm -e SHEEPCRM_FLOCK=your-flock -e SHEEPCRM_API_KEY=your-key -- node /absolute/path/to/sheepcrm-mcp/dist/index.jsVariable | Required | Meaning |
| yes | Your database (flock/bucket) name. Letters, digits, |
| yes | Your API key, sent as a Bearer token. |
| no |
|
| no | Defaults to |
| no | Seconds one tool call may spend on requests, retries and waits (2 to 55, default 45). Keep it below your MCP client's request timeout. One test uses 5. |
Safety defaults
Read-only unless
SHEEPCRM_ALLOW_WRITES=true. Read tools carry the MCPreadOnlyHintannotation.set_event_attendanceis markeddestructiveHint: true(it overwrites the previous status) andidempotentHint: true.Members, attendees and buyers are third parties. By default:
search results and attendance lists return names and record URIs, not the
primary_email,primary_telephoneorattendee_emailfields;invoices return the buyer's name, not their address, locality, region, postcode, country, email, telephone or VAT number;
events do not return the booking and billing contact, the booking organisation or the venue postcode;
the three records whose shape sheepCRM does not document (
get_person,get_membership,find_person) are passed through with every key whose name suggests contact or personal data withheld and listed underwithheld_fields: email, phone, telephone, tel, mobile, fax, address, street, town, city, county, region, postcode, postal code, zip, locality, mailsort, geo, coordinates, lat, lng, lon, latitude, longitude, dob, born, birth, date of birth, death, deceased, date of death, salary, pay, wage, income, NI and national insurance, passport, licence (including driving licence), NHS, IP, user agent, health, medical, SEN, EDI, equal ops, ethnicity, gender, sexuality, disability, religion, emergency, next of kin, pickup (authorised pickup), known as, school, school year, photo, vehicle, Twitter, Facebook, Instagram, Skype, LinkedIn and social. Of the person record shown in the legacy docs' Getting Started example, this withholds the contact, location, identity, health, education, photo and social fields (email,telephone,address_lines,locality,region,postal_code,mailsort_code,geo,date_of_birth,date_of_death,deceased,driving_licence,sen,emergency_contact_details,authorised_pickup,known_as,gender,school,school_year,photo,twitter_username,linkedin_public_profileand the other social usernames), and the tests check each of them. Names, salutation, job title, interests, tags,bio,anniversary,adultand similar fields in that record are not withheld by name (their text is still redacted as below). The key is normalised first (camelCase split, lower-cased) and matched as whole words, soprimary_email,dateOfBirth,address_linesandpostal_codeare caught anddescriptionortitleare not. Keys are redacted like text too: an object keyed by an email address shows[email redacted]as the key, and the paths underwithheld_fieldsandnever_returneduse the redacted key. A key that holds personal data under another name (a free-formnotesobject, say) is not caught by name;every other string that can hold free text (names, titles, descriptions, lapse reasons, text inside undocumented records, error messages) has email addresses replaced by
[email redacted], phone-number-like sequences by[phone redacted], UK postcodes by[postcode redacted]and a date introduced by "born", "DOB", "date of birth", "birth date" or "birthday" by[date of birth redacted]. These are heuristics: they do not catch a street address, a health condition in a sentence or a date of birth written without one of those words;include_contact_details: trueon a tool returns those fields and that text as stored (except the items below).
Never returned, whatever is asked: in membership records
payment_method,payment_plan,payment_date,payment_reference,gc_subscription_id,stripe_subscription_idandnext_payment_plan; in invoices the organisation'spayment_detailsandpayment_detail_notes(bank details),signature,signed_invoice_uriandpayment_reference_uri(they give access to the invoice or payment), andlinks(PDF download links); ticketaccess_codes; an event's internal notes (dietary requirements, catering, bar, setup, general notes); segment rules and include/exclude lists; and in undocumented records any key whose name suggests bank, card or payment data (bank, IBAN, BIC, SWIFT, sort code, account number, card, PAN, BIN, last4, CVV, CVC, payment, Stripe, GoCardless, gc, mandate, PayPal, direct debit, dd), listed undernever_returned. Card numbers that pass the Luhn check and a sort code followed by an 8-digit account number are replaced in all text in every mode.Nothing is downloaded: PDF, Excel, export, avatar and image endpoints are not used.
IDs are checked before any call is made: uids must be alphanumeric (the spec describes them as "an alphanumeric unique identifier"; its examples are 24 and 8 hex characters), contact URIs must have the form
/flock/type/uid/, belong toSHEEPCRM_FLOCKand be of the right type (person or organisation; person for attendance), and enumerated values (state,invoice_type, attendancestatus) must be one of the documented values.Rate limits: the OpenAPI spec says nothing about them. The legacy docs' "API Throttling" section says sheepCRM may change its limits at any time, that throttled endpoints return
x-rate-limit-limit,x-rate-limit-remainingandx-rate-limit-resetheaders, and gives the example40, 400;window=60, 1000;window=3600(40 calls remaining, 400 per minute, 1000 per hour). They do not say what status a throttled request gets or whetherRetry-Afteris sent. This server spaces requests 250 ms apart (at most four a second, which is inside 400 a minute but, sustained, would use the example's 1000 an hour in about four minutes) and retries a 429 at most twice for any method. It waits forRetry-After(whole or fractional seconds, or an HTTP-date); without it, forx-rate-limit-resetwhen that is a plain number of seconds; with neither, 2 s then 4 s. It does not readx-rate-limit-limitorx-rate-limit-remaining. A single wait longer than 10 seconds is not attempted: the call gives up at once and says how long sheepCRM asked to wait. A listing makes at most 10 page requests per tool call (1000 items at 100 per page).Time budget: all the requests, retries and waits of one tool call share a budget of 45 seconds (
SHEEPCRM_TIME_BUDGET_S), below the MCP client's default 60-second request timeout. A wait that would run past it is not started, and a request still unanswered when it runs out is abandoned. A listing that has already fetched some pages then returns them withcomplete: falseand a note saying why it stopped and which page to continue from (it does the same when a later page stays rate limited after its retries); any other call returns an error saying the budget ran out. If aPUT .../attendanceis abandoned mid-request, the error says it may already have been applied.502, 503 and 504 are retried the same way for
GETonly; after three failures the error says the service may be unavailable, without the gateway's HTML. APUT .../attendanceis never retried after a gateway error, because it may already have been applied; the error says to checklist_event_attendeesfirst.The legacy docs ask integrations to send an
APPLICATIONheader identifying themselves; the server sendsAPPLICATION: sheepcrm-mcp/0.1.0.A 200 whose body is not JSON (a proxy or login page in the way) is reported as an error naming
SHEEPCRM_BASE_URL, never as an empty list. A paginated response without its documented list (bookings,segments) is reported as an error naming the keys received.A rejected key (401 or 403) produces a message naming
SHEEPCRM_API_KEY, where keys come from,SHEEPCRM_FLOCKand the possibility of missing permissions. A 404 says to check the id or URI and mentions that the legacy docs say a 404 can also mean bad credentials. Error text from sheepCRM is redacted like any other text, and the API key is replaced by[redacted]if a response echoes it. Both happen on the whole text before it is shortened for the message, so a key or email address that straddles the cut leaves no fragment.
Tests
npm testThe test suite (27 checks, about 45 seconds; most of it is real retry and time-budget waiting):
Checks the spec (downloaded from
sls-api.sheepcrm.com/.well-known/openapi.yamltospec.yamlon the first run) against what this README says about it: OpenAPI 3.0.0, thesls-api.sheepcrm.comserver, nosecuritySchemesand nosecurity, no documented 200 body for the contact summary, member detail and find-person endpoints, the member-detail path written without the slash after/api/v2, and the documented enums for eventstateand attendancestatus. It also checks that the mock's API keys are marked as fake and appear nowhere in the spec. Then it validates every fixture record against the spec's component schemas (SearchResultsResponse,PersonMembershipAll,EventSingle,EventsList,EventAvailableTickets,EventAttendanceList,invoice,single_segment,all,count) with Ajv. The contact summary, member detail and find-person records have no published schema and no published example: their fixtures are shapes assumed for the tests (labelled as such intest/fixtures.mjs) and are not validated against anything; the server treats them as opaque records.Starts a local mock of the API that serves the fixtures with
page/page_sizepagination, requiresAuthorization: Bearer(401 without it, 403 for a wrong key or another flock), answers 404 for unknown ids and 400 for a missingqorname, and answers the firstGET .../available_ticketswith a 429 andRetry-After: 1. The mock's list, detail, write (UpdatedEventAttendance) and error (error: 400, 401, 403, 404, 429) responses are validated against the spec's schemas. No schema in the spec marks any field as required, so a schema pass only proves the types of the fields present; the documented top-level keys of the list, attendance, membership, write and count responses are asserted explicitly. The spec gives no example error body, so the mock's error texts are placeholders in the documented{error, description, type}shape, and the 404 on the events endpoints (which document only 200, 400 and 401) is an assumption.Starts the built server and drives it over stdio with the official MCP client: 24 checks covering the tool list and annotations; every read tool; the global search with its local
resourcefilter; every documentedfind_personparameter passed through under its documented name, and the matching-rule note; withholding of contact, birth, address and health fields (including each withheld field of the person record in the legacy docs' Getting Started example, and birth-date keys namedbornanddateofbirthin a nested record) and redaction of emails, phone numbers, postcodes and dates of birth in the text of the undocumented records by default and their return on request; an object keyed by an email address redacted in the record and in thewithheld_fieldspath, and one keyed by a card number replaced even on request; bank, card and payment fields absent and card and bank numbers in text replaced even on request; memberships sorted with computed days until the end and no payment fields in either mode; event paging across two pages ending at a short page with requests at least 200 ms apart, astatepassed in the path ending at an empty page after a full one,max_resultswith continuation from the next page; event details without internal notes or ticket access codes, with the booking contact and venue postcode only on request; attendance counts, the local status filter and emails only on request; invoice buyer contact only on request and bank details, signature and PDF links never; the three segment endpoints, the refusal ofnamewithinclude_inactive, and segment counts; the write gate with the variable unset and set tofalse; thePUT .../attendancequery parameters validated against the spec's parameter schemas, no body, URI normalisation, and the local refusal of an organisation URI or another flock's URI; bad ids and URIs, an eventstateand attendance statuses outside the documented enums refused before any request; 404 messages; the 429 retry waiting forRetry-Afterin the seconds, fractional-seconds and HTTP-date forms and the 2 s fallback without the header,x-rate-limit-resetused whenRetry-Afteris absent (and a reset above the cap giving up at once); giving up after three attempts a second apart on a persistent 429, a listing whose second page stays rate limited returning its first page with a note, and giving up at once on aRetry-Afterabove the cap; with a 5-second budget, a listing rate limited on two pages returning its first page before the budget runs out, a single rate-limited call stopping before its next wait would pass the budget, a slowPUTabandoned at the budget with the "may already have been applied" advice, and out-of-rangeSHEEPCRM_TIME_BUDGET_Svalues refused at start-up; a 502 retried for aGET, three 503s reported with advice and without HTML, a 502 on thePUTnot retried; a 200 with a non-JSON body, and one without the documentedsegmentslist, reported as errors; an API key echoed in an error body replaced by[redacted], and no fragment of it left when an echo straddles the point where the quoted text is cut (for a non-JSON 200 and for a 500); that every request carriedBearer <key>, theAPPLICATIONheader and the configured flock, and matched a documented method and path (with the member-detail slash corrected as described under Status, and the organisation form ofmembership/allas the one named exception); the 403 message for a wrong key; and the server refusing to start with a missing or malformedSHEEPCRM_FLOCK.
Status
This is a working prototype. It has not yet been run against the live API, because it was built without a sheepCRM account (there is no self-serve trial; a test flock has to come from sheepCRM). Everything below comes from the published spec and legacy docs and should be confirmed on a real flock:
The shapes of
GET /api/v2{contact_uri}summary,GET /api/v2/{bucket}/member/{uid}/detailandGET /search/v2/{bucket}/person. The spec documents no body for these; the mock's records are guesses. The server does not depend on their field names, but its default output is only as safe as the key-name list above, so check what a real record contains (in particular any free-form fields that hold personal data under a neutral name) before relying on the default view.Memberships of an organisation. The spec documents
membership/allfor a person only ({person_uri}); the legacy docs' Contacts page documentsGET /api/v2/{bucket}/{organisation|person}/{uid}/membership/alland the vendor's README route list includesGET /api/v2/{bucket}/organisation/{uid}/membership/all, but neither documents its response for an organisation. The server calls it for organisation URIs and assumes the samePersonMembershipAllshape.The personal field names. The key-name list is checked against the person record in the legacy docs, which is a v1 API example; the v2 summary, member detail and find-person responses may use other names.
The member detail path. The spec writes it as
/api/v2{bucket}/member/{uid}/detail(no slash after/api/v2), while the vendor's README lists/api/v2/{bucket}/member/{uid}/detail; the server calls the latter.Whether a Bearer API key from Profile settings works on
sls-api.sheepcrm.comfor every endpoint used. The legacy docs show Bearer keys onapi.sheepcrm.com/api/v1and on the v2 people and segment examples; the spec declares no security scheme at all.The status for a bad key (the spec says 401 for invalid credentials and 403 for missing privileges; the legacy docs say 403 for a problem with the key or permissions, and that 404 is "also returned on bad user credentials"), and the real error body shape.
Pagination. The spec documents
pageandpage_size(events: defaults 1 and 250) but no total and no next link. The server asks for 100 per page (fewer whenmax_resultsis smaller) and takes a page shorter than that, or an empty one, as the end. If sheepCRM capspage_sizebelow 100 without saying so, listings would stop after the first page; if it ignorespage, pages would repeat.Whether
GET /events/v2/{bucket}/booking/{uid}/attendanceand.../attendance/alldiffer (the spec describes both as "All the attendees for an event"; the server uses the first), and whether attendance lists are complete for large events (no pagination is documented).GET /search/v2/{bucket}: how many results it returns and whether it pages (nothing is documented;max_resultsis applied locally), and the full set ofresourcevalues.The
find_personmatching rules, which come from the legacy v1 docs; the spec says the v2 endpoint is the same.The meaning of
end_dateon memberships (the last day of membership is assumed;days_until_endis computed from it in UTC) and ofmembership_record_statusvalues (the spec lists no enum).PUT .../attendancefor a person who is not yet on the list (whether it adds them or refuses), and its errors.404s on the events and segments endpoints, which document only 200, 400 and 401.
Rate limiting: the real limits, the status of a throttled response, whether
Retry-Afteris sent, and the exact format ofx-rate-limit-reset(it is only used when it is a plain number; see Safety defaults).Whether the
APPLICATIONheader, documented for the v1 API, matters on v2.
Going to production
This version runs locally over stdio, with the user's own API key. For membership teams to connect from claude.ai or ChatGPT without handling keys, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by sheepCRM (its OAuth2 client registration is already documented), a run of the test suite against a real flock to settle the points above, formatters for the contact summary and member detail once their shapes are known, and then a listing in the Claude and ChatGPT connector directories.
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
Create forms, read submissions, and build invitations from Claude, ChatGPT, or any MCP client.
API-first CRM for LLMs - contacts, companies, deals and activities over a native MCP server.
Hosted MCP server for Cliniko — patients, appointments, availability, and invoices for AI agents.
Let AI agents query data and act across all your business apps via MCP.