Stampede 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., "@Stampede MCP serverFind Sam Evans. When did we last see him, and what is he tagged with?"
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.
Stampede MCP server
An MCP server that lets Claude, ChatGPT and other MCP clients work with a Stampede hospitality guest CRM: the venues in an organisation, guest profiles with their tags and orders, the organisation's deals, and which guest owns a Wi-Fi device; and (when enabled) tagging a guest. It is built from Stampede's public developer documentation only (the HTML pages under https://stampede.ai/developer). Stampede publishes no OpenAPI or other machine-readable spec: its pages describe each response as a TypeScript-style shape without example values, so the JSON schemas used by the tests were written by hand from those shapes (see Tests).
Once it's connected, someone running a venue group can ask things like:
"Which venues are in our organisation, and what are their IDs?"
"Find Sam Evans. When did we last see him, and what is he tagged with?"
"What has this guest ordered with us?"
"Which of our deals are active, when do they expire, and how many codes does each have?"
"Whose phone is 3c:22:fb:0a:1b:2c on the guest Wi-Fi?"
With writes enabled: "Tag Priya Shah as a Wine Club member."
Tools
Tool | What it does | API calls |
| Venues (serials) in the organisation the API key belongs to: ID, name, organisation ID. Pages with the documented |
|
| One venue by its ID (as |
|
| Guests matching the documented |
|
| One guest profile: name, dates, the profile's |
|
| A guest's tags. The response is not documented, so records are passed through with the generic withholding below. |
|
| A guest's orders, with the documented |
|
| Deals (vouchers such as |
|
| Which guest owns a device, by MAC address, sent as the documented |
|
| Adds a new or existing tag to a guest with the documented body |
|
Not covered on purpose: creating guests, bookings, orders, Wi-Fi sessions, form submissions and deals (POST /v1/guests, .../booking, .../order, .../wifi, .../form, POST /v1/deal), removing a tag (DELETE .../tags/by-name), voucher-code search and revocation (GET /v1/code/search, whose documented response includes sent_to_profile_data typed any, and POST /v1/code/revoke), and both workflow triggers (POST /v1/workflows/incoming-event, POST /v1/workflows/{workflowId}/workflow-runs).
Related MCP server: Indraft
Setup
Requires Node 18 or later.
npm install
npm run buildYou need a client ID and client secret. As the Authentication page documents, they are created in the Stampede dashboard under Marketplace > API Keys > Create New API Key, and a key belongs to one organisation, so create it in the organisation whose venues and guests you want. The server exchanges them for a Bearer token at POST https://global.stampede.ai/oauth/token (OAuth 2.0 client credentials) and sends that token on every call.
Claude Desktop: add this to claude_desktop_config.json:
{
"mcpServers": {
"stampede": {
"command": "node",
"args": ["/absolute/path/to/stampede-mcp/dist/index.js"],
"env": { "STAMPEDE_CLIENT_ID": "your-client-id", "STAMPEDE_CLIENT_SECRET": "your-client-secret" }
}
}
}Claude Code:
claude mcp add stampede -e STAMPEDE_CLIENT_ID=your-client-id -e STAMPEDE_CLIENT_SECRET=your-client-secret -- node /absolute/path/to/stampede-mcp/dist/index.jsVariable | Required | Meaning |
| yes | The client ID of your API key. |
| yes | Its client secret. Sent only in the body of the token request, never logged. |
| no |
|
| no | How many API requests this server may make in any 15 minutes, from 1 to 100. Defaults to 100, the documented limit; lower it if other integrations use the same key. A value outside 1 to 100 stops the server at start-up. |
| no | Seconds one tool call may take, all its requests and waits included. Defaults to 45, under the MCP client's default 60-second timeout. Used by the tests. |
| no | Defaults to |
Safety defaults
Read-only unless
STAMPEDE_ALLOW_WRITES=true. Read tools carry the MCPreadOnlyHintannotation.tag_guestis marked as a write that is not destructive; it is not marked idempotent, because the Tags page does not say what adding a tag the guest already has does.Guests are third parties. By default the tools return a guest's IDs, name, created and last-interaction dates and tag IDs (and, in
get_guest, the profile'sverifiednumber and how many personalisation choices and custom answers there are), and nothing else about them:search_guestswithholdsemail,phoneand the three marketing-consent timestamps (data_opt_in_at,email_opt_in_at,sms_opt_in_at) and lists them underwithheld_fields;get_guestalso withholdslat,lng,birth_day,birth_month,postcode,countryandgender, and withholds the documentedorganization_registration_personalisation_choiceandcustom_question_answerslists as a whole (their items are typedany), returning only how many there are;wifi_lookupwithholds the owner'semail;names are returned, with any email address, phone number or UK postcode typed into them replaced by
[email redacted]/[phone redacted]/[postcode redacted];include_contact_details=truereturns all of it.
The tag and order records are passed through as sent, because Stampede does not document their shape. Any key whose name suggests a guest's personal data (email, phone, telephone, tel, mobile, cell, msisdn, fax, contact, address, addr, street, city, town, county, postcode, post code, postal, zip, dob, birth, lat, lng, gender, ip, user agent, mac, opt in, consent, user profile) is withheld unless
include_contact_details=true, and listed underwithheld_fields. Any key whose name suggests bank, card or payment data (card, bank, iban, bic, swift, sort code, account number, pan, bin, last4, cvv, cvc, payment, pay method) is dropped whatever the caller asks, and listed underdropped_payment_fields. Keys are matched as whole words after splitting camelCase and snake_case, socustomer_email,contact_number,postalCode,delivery_postal_code,address_line_1,card_last4andpaymentare caught whilevenue_ids,in_venueandnotesare not; a field that holds personal data under a name without one of those words is not caught. Key names are redacted like values (an email address used as a key is replaced).Free text (venue names, deal names and descriptions, tag and order values, error messages) goes through an email pattern and a phone-number heuristic (international numbers with + or 00, bracketed UK area codes, and UK numbers starting with 0), plus two UK patterns: a heuristic that covers international numbers written with
+or00, UK numbers with a bracketed area code, UK-style0…numbers of 9 to 11 digits, and UK numbers written as44followed by 10 digits without+(447700900123); other digit strings starting with0are redacted too. UK postcodes (EH6 6AA,SW1A 1AA, in either case, with or without the space) are replaced with[postcode redacted]; postal codes of other countries are not. A street name or town written into free text (an order note such as "Deliver to 1 Example Street, Edinburgh") is not caught, because there is no reliable pattern for it; the tests show this. Venue and deal text is always redacted (those tools have noinclude_contact_detailsswitch). Card numbers (13 to 19 digits that pass the Luhn check) are replaced with[card number redacted]everywhere, with or withoutinclude_contact_details.Nothing is downloaded: the branding image URLs of a venue are returned as links.
Input is checked before any call is made: argument names that a tool does not know are refused (so a typo such as
serachcannot turn a search into a full listing that spends the request budget); a venue ID must be 1 to 100 characters (the Venues page types it only asstring, andget_venuecompares it in memory, never in a URL); guest IDs must be a single path segment of letters, digits,_and-(the docs type them as strings and give no example) and may not bewifi, sinceGET /v1/guests/wifiis the Wi-Fi lookup; MAC addresses must be six hex pairs separated by:or-, or twelve hex digits; a tag name is trimmed and must be 1 to 255 characters (255 is the limit the Tags page gives fortag_namewhen deleting a tag, assumed here for adding one).Rate limits, as documented: the Introduction page says "Users are allowed up to 100 requests every 15 minutes"; the Authentication page says the token endpoint "operates a strict rate limit of 20 requests/minute" and asks integrations to cache the token until it approaches expiry. This server keeps a rolling budget of 100 API requests (or
STAMPEDE_REQUESTS_PER_15_MIN) per 15 minutes per server process, retries included. When the budget is spent it waits for a free slot if that takes 10 seconds or less and fits in the tool call's time budget (a case the tests do not exercise), and otherwise refuses the call without sending it, saying how many seconds to wait. The token is cached and refreshed shortly beforeexpires_inruns out (a minute before, or after half its lifetime when it lives less than two minutes), and fetched afresh once if an API call answers 401. Token requests have their own budget of 20 per minute, and all requests are spaced 250 ms apart; neither of those two is exercised by the tests. The budget cannot see requests made by other processes or integrations that use the same key.Tools that page ask for 50 records per page at most and stop at
max_results, so a question does not spend the budget by default:search_guestsandlist_ordersreturn 25 guests or orders unless asked for more. Each call also reads at most a fixed number of pages (10 forlist_venuesandlist_deals, 4 forsearch_guestsandlist_orders); when a listing stops early,notesays whether it hitmax_results, the page cap or the time budget, and how to continue (cursorset tonext_cursor, orpageset tonext_pagefor deals), or that there is no documented way to.Stampede does not say that
limitis honoured. If a page holds more records than were asked for, the whole page is returned (socountcan exceedmax_results, andnotesays why): cutting it would makenext_cursor, which points after the whole page, skip the records that were cut. A list sent as a bare array (no links, so no cursor) is cut tomax_resultsand marked incomplete.A 429 is retried at most twice for any method, waiting for
Retry-After(whole or fractional seconds, or an HTTP-date; 2 s then 4 s when the header is absent). Each wait is capped at 10 seconds: if Stampede asks for a longer wait, the call gives up at once and the message says how long to wait. What Stampede's 429 looks like, and whether it sendsRetry-After, is not documented.Every tool call has one time budget, 45 seconds by default (
STAMPEDE_TOOL_BUDGET_S), shared by all its requests and waits: token requests, API retries and the 401 re-authentication together, so the call ends before the MCP client's default 60-second timeout instead of sending requests after the client has given up. A retry or rate-budget wait that would end past it is not started, and a request still running when it ends is aborted (forPUT .../tagsthe message then says the tag may already have been added). A listing that has read at least one page when the budget runs out returns those records, marked incomplete, with the cursor of the next page. Aborting a request that is still running at the deadline is not exercised by the tests; the skipped waits and the partial listing are.502, 503 and 504 are retried the same way for
GET(and for the token request, a case the tests do not exercise); when all three attempts fail the error says the service may be unavailable, without the gateway's HTML.PUT .../tagsis never retried after a gateway error, because the tag may already have been added; the error says to check with the matching read tool (list_guest_tags) before repeating it.Pages are followed by reading the query parameters of
links.next(cursorfor venues, guests and orders,pagefor deals) and requesting the same path again fromSTAMPEDE_BASE_URL, keeping the originalsearch. The host inlinks.nextis never contacted, so the token only goes to the configured base URL. Alinks.nextthat names a different path stops paging and says so.A 200 to a
GETwhose body is not JSON (a proxy or login page in the way) is reported as an error namingSTAMPEDE_BASE_URL, and a list response without adatalist as an error naming the keys it had; neither is ever shown as an empty list.PUT .../tagshas no documented response, so any 2xx is taken as "tag added" whatever its body (empty, text or JSON), and only amessagefrom a JSON object is passed on.wifi_lookupreads a 404 as "no guest owns this device" only when the 404 body is JSON (from the API); a 404 page from a proxy or a moved route, a 401 that survives a fresh token, and repeated 5xx stay errors.An invalid or expired
cursor(or dealspage, a case the tests do not exercise) that Stampede answers with 400 comes back as an error that says to call again without it.Errors pass on Stampede's
messageanderrors(and the token endpoint'serroranderror_description), redacted, never thestackfield its documented error body carries. The client secret is replaced with[redacted]if it ever appears in text that is passed on (so is the current access token, a case the tests do not exercise). Wrong credentials produce a message that namesSTAMPEDE_CLIENT_IDandSTAMPEDE_CLIENT_SECRETand where they come from.
Tests
npm testThe suite needs no Stampede account and no network after the first run. It:
Checks the documentation and the schemas written from it. The 12 developer pages are downloaded to
docs.htmlanddocs/on the first run (both gitignored) and parsed: the suite asserts that they list 20 operations, including every method and path this server calls, with the documented query parameters (cursor,limitfor venues;search,limit,cursorfor guests and orders;macfor the Wi-Fi lookup; none for deals), that the Tags and Order pages document no response for the reads used here, that the two rate-limit sentences and the Form page'svenue_idrule (see Status) are still on their pages, and that each response shape copied verbatim intotest/documented-examples.mjs(token 200 and 400, venue list, guest list, single guest, the 400 and 409 error bodies, Wi-Fi owner, deal list) is still on its page. Each hand-written schema intest/schemas.mjsmust equal the schema thattest/shape.mjsderives mechanically from its shape (every key required unless marked?, no other keys, the documented types and nulls), and the two request-body schemas (token request, tag) must list exactly the documented body fields and required fields. The mock's client ID and secret are checked to be obviously fake values that appear nowhere in the documentation. Every fixture record is then validated against the schemas.Starts a local mock of
https://global.stampede.ai: the token endpoint with the documented JSON body, answering 200 or the documented 400 shape; the/v1endpoints behind a Bearer token the mock issued; cursor pagination whoselinks.nextpoints athttps://global.stampede.ai/...; deals paginated by page withlinksandmeta; a one-off 429 withRetry-After: 1on the firstGET /v1/venues; a 400 in the documented error shape for a cursor it did not issue; injected answers (JSON, HTML or raw text bodies, any status) on any endpoint; and a record of every request. The mock's token, list, guest, Wi-Fi, deal and 400 responses are validated against the schemas. Stampede documents no example values, so every value in the fixtures is made up, and it documents no 401, 404 or 429 body, so the mock answers those with the documented 409 error shape (401, 404) or a baremessage(429). The tag and order records the mock serves are assumptions (see Status).Starts the built server and drives it over stdio with the official MCP client, 29 checks (32 in the whole suite, about 45 seconds): tool list and annotations; the token request's body and content type, one token reused across calls, a revoked token replaced once, a short-lived token refreshed before it expires; every tool; cursor paging to
links.next: nullfor venues, guests and orders, with continuation bynext_cursorfor venues and guests and thesearchkept on later pages; deals pagedpage=2,page=3fromlinks.next;search,cursor,limitandmacpassed through exactly; the default withholding and its listing, redaction of emails and phone numbers in names, venue names, deal descriptions, tag names and order notes, and their return on request; payment fields dropped and a card number redacted even withinclude_contact_details;tag_guestsending a body that validates against the documented request body; the write tool absent withSTAMPEDE_ALLOW_WRITESunset orfalse; bad IDs, MAC addresses, cursors, tag names and unknown argument names (serach) refused before any request;get_venuehonouringmax_pages; the 404 message for an unknown guest, without the mock'sstack, and a 422 whosemessageanderrorsare passed on with an email and a phone number redacted; the 429 retry for the seconds, fractional-seconds, HTTP-date and missing-header forms, giving up after three attempts and at once for a wait over 10 s; a 502 retried forGET, three 503s reported with advice, a 502 on thePUTnot retried; a 200 HTML page and a list withoutdatareported as errors; alinks.nextto another path stopping the paging; the first server process staying under 100 requests; an undocumented order record withcontact_number,postalCode,street,city,msisdn,address_line_1,delivery_postal_code,townandcountykeys withheld, a UK postcode and a44…number redacted in its note, in a key and in a 400 error message, and a street and town in the note left as they are (the known gap); a page larger than thelimitsent kept whole so thatnext_cursorskips nothing, and a bare array cut tomax_results; the page cap (4 pages of guests, 10 of deals) reported as such with a cursor ornext_pageto continue, and amax_resultsalready at its maximum not advising to raise it; an invalid cursor answered with 400 reported with advice;wifi_lookupgivingfound: falseonly for a JSON 404, and errors for an HTML 404, a 401 after a fresh token and three 503s, with an email in the owner's name redacted;tag_guestsucceeding on an empty 200, an empty 201, a textOKand a JSONtrue; a 3-second tool budget stopping a chain of 2-second 429 waits (list andPUT) inside it, and a listing returning its first page with a cursor when the second runs out of time; wrong credentials giving an actionable message with the secret scrubbed from an echoederror_description; the request budget refusing a call locally, without sending it, onceSTAMPEDE_REQUESTS_PER_15_MINis spent, with a value of 500 refused at start-up (and aSTAMPEDE_TOOL_BUDGET_Sof 0); and, as the last check, that every request made by every server process in the suite carried the token request's documented body or a Bearer token the mock issued, hit a documented method and path, and used only documented query parameters (pluspageonGET /v1/deal, read fromlinks.next).
Status
This is a working prototype. It has not been run against the live API, because it was built without a Stampede account: there is no free trial, pricing is quoted on a sales call, and API keys are created inside the dashboard. Everything below comes from the public pages and should be confirmed on a real account:
The token request. The server sends
client_id,client_secretandgrant_typeas JSON, as every example on the Authentication page does, and noscope(the page shows"scope": "ALL:ALL"in the response only). Confirm that this works, whatexpires_inis in practice, and what a wrong secret gets (the page documents a 400 witherroranderror_description; the server treats 400, 401 and 403 from the token endpoint as a credentials problem).The answer to an expired or revoked token, and to an unknown guest, venue or Wi-Fi device. Nothing is documented: the server treats a 401 as "fetch a new token and retry once", a 404 as "not found", and a 404 with a JSON body from the Wi-Fi lookup as "no guest owns this device"; the mock uses the documented 409 error shape for them.
The format of guest IDs (the docs type them as strings with no example), and whether they can contain anything other than letters, digits,
_and-. The Guests and Tags pages listguest_idunder "Query Parameters" forGET /v1/guests/:guest_idandGET /v1/guests/:guest_id/tags, while their path templates put it in the path; the server follows the path templates and assumes the label is a slip in the docs.Venue IDs: the Venues page types
idonly asstring; the Form page says itsvenue_idbody field is "exactly 12 alphanumeric characters". Whether the two are the same value is not stated, soget_venueaccepts any ID and matches it exactly against the list.The meaning and values of
user_profile.verified(typednumberon the Guests page, with no explanation).get_guestreturns it as sent, and does not read it as "email verified".Pagination: the default and maximum
limitfor venues, guests and orders, and whether it is honoured at all (not documented; the guest table typeslimitas "Date", and the server sends a number of at most 50 and keeps an oversized page whole), whatlinks.nextcontains (the server keeps the originalsearchandlimitand overrides them with whateverlinks.nextcarries), whetherlinks.firstandlinks.lastare null on cursor pages, and whetherGET /v1/dealpages bypage(the server followslinks.next, sends no parameters of its own on a first call because none is documented, and sendspageonly when continuing from anext_pageit read fromlinks.next).What
searchmatches onGET /v1/guestsand onGET /v1/guests/:guest_id/order(fields, partial or exact, case). The mock matches names, emails and phone numbers for guests, and any field for orders.The shape of
GET /v1/guests/:guest_id/tags,GET /v1/guests/:guest_id/orderandPUT /v1/guests/:guest_id/tags, none of which is documented. The mock serves tags as{ "data": [{ "id", "name" }] }(and one guest's as a bare array) and orders as{ "data": [...], "links": {...} }with the fields of the documented order-creation body (amount,currency,venue_ids,event_id,in_venue) plusidandcreated_at; the server does not depend on those names, but its default output for these two tools is only as safe as the key-name heuristic above, so check what a real record contains before relying on it.Whether
PUT .../tagswith a tag the guest already has is a no-op, what it returns, and the longest tag name it accepts.The Wi-Fi lookup: which MAC formats it accepts (the server sends the address exactly as typed, in either case, with
:or-or no separators) and whether the match is case-sensitive.The rate limit: whether the 100 requests per 15 minutes are counted per API key, per organisation or per IP address, whether token requests count towards it, and what a 429 looks like (body and
Retry-After). The server's budget assumes 100 per key and does not count token requests.The
gendervalues (the table on the Guests page is cut off after"m"and"f"), the units oflatandlng, and whethercountryis a code or a name.Whether guests whose
data_opt_in_atis null (no data opt-in) are returned byGET /v1/guests, and whether the business wants their names shown at all.
get_venue pages through the venue list because no single-venue endpoint is documented; on a large organisation a GET /v1/venues/:id would make it one request.
Going to production
This version runs locally over stdio, with the organisation's own client credentials. For venue groups to connect from claude.ai or ChatGPT without handling keys, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by Stampede, a run of this suite against a real account to settle the points above, and then a listing in the Claude and ChatGPT connector directories. A third-party build would also go through Stampede's "Going Live" review to be listed in the Stampede Marketplace.
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
API-first CRM for LLMs - contacts, companies, deals and activities over a native MCP server.
The HubSpot MCP Server acts as a bridge that enables AI assistants and Large Language Models to securely interact with HubSpot CRM data through natural conversation, without requiring users to understand complex API structures. It provides read-only access to standard CRM objects (contacts, companies, deals, tickets, products, invoices, and more) and their associations, secured via OAuth 2.0, allowing AI agents to perform tasks like summarizing deals, fetching company updates, and looking up record changes.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
CRM + visual automation builder AI agents can drive via MCP: contacts, tags, maps, email/SMS flows.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceConnects Claude to Cendyn CRM, enabling real-time retrieval of contacts, purchases, campaigns, and audiences for analysis and decision-making.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to read and write CRM data—companies, contacts, opportunities, tasks, and interactions—through MCP, with OAuth and provenance tracking for every value.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to manage a fully customizable CRM, including contacts, companies, sales pipelines, custom objects and fields, file uploads, and data relationships via MCP over HTTP.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