HelloAsso for associations: members, tickets, donations, payments, refunds, checkouts.
Your association's HelloAsso, answered by asking.
A single Rust binary that puts the whole HelloAsso v5 API — memberships, tickets, donations, payments, refunds, checkouts — in front of any MCP client.
🇫🇷 Serveur MCP pour HelloAsso : adhérents, billetterie, dons, paiements et remboursements, interrogés en langage naturel depuis Claude, Cursor ou tout client MCP.
Ask your assistant "how much did we collect last month?", "is Camille Martin's membership paid?", "give me the attendee list of the gala" — and get the answer from the same HelloAsso your treasurer reconciles against.
> how much did we collect in September, and on what?
summarize_payments 2026-09 · 214 payments read
collected 8 342,00 € (196 authorized)
Membership/adhesion-2026 5 160,00 € 61.9%
Event/gala-2026 2 730,00 € 32.7%
Donation/dons 452,00 € 5.4%
by means: Card 7 912,00 € · Sepa 430,00 €
refunded 60,00 € · 3 refused · 1 contested
> who registered for the gala, with their dietary answers?
list_form_items form_url = helloasso.com/associations/les-amis-du-velo/evenements/gala-2026
item_states = Processed, Registered · with_details = true
87 participants, 3 tiers, custom field "Régime alimentaire"
Paste a HelloAsso URL, say last month, name a person. The server resolves the organization, the form, the period and the pagination.
HelloAsso's API is a clean REST surface, but handing it raw to a language model goes wrong in predictable ways: amounts are cents, a form is a (type, slug) pair nobody remembers, date ranges are half-open, tokens expire every 30 minutes, and the one call that matters most — a refund — sends real money back with no undo.
whoami and summarize_payments, which exist because the API has no "who am I"
for a client and no aggregate. A test fails if that count ever drops.evenements, adhésions).
Periods read last month, 2026-09, last 30 days. Persons are found with search.summarize_payments walks every page and splits what was
actually collected (Authorized) per form, payment means and month, in cents and
euros — no model adding up page one and calling it a total.refund_payment and cancel_order refuse to run
without confirm: true, and the refusal states the amount and the payer so the
confirmation is an informed one. HELLOASSO_READ_ONLY=1 withholds every write tool.client_credentials once, then
refresh_token — never a new grant while a refresh works, as the API requires. A token
revoked server-side is renewed transparently.Download the archive for your platform from the
latest release,
check it against its .sha256, and put mcp-helloasso on your PATH:
# macOS on Apple silicon; swap the target for x86_64-apple-darwin,
# x86_64-unknown-linux-gnu or aarch64-unknown-linux-gnu
V=v0.1.1 T=aarch64-apple-darwin
curl -LO https://github.com/edouard-claude/helloasso-mcp/releases/download/$V/mcp-helloasso-$V-$T.tar.gz
tar xzf mcp-helloasso-$V-$T.tar.gz
sudo mv mcp-helloasso-$V-$T/mcp-helloasso /usr/local/bin/
Windows: take the x86_64-pc-windows-msvc.zip archive.
git clone https://github.com/edouard-claude/helloasso-mcp
cd helloasso-mcp
cargo build --release
# the binary is at target/release/mcp-helloasso
cargo install --git https://github.com/edouard-claude/helloasso-mcp
docker run --rm -i \
-e HELLOASSO_CLIENT_ID=… -e HELLOASSO_CLIENT_SECRET=… \
ghcr.io/edouard-claude/helloasso-mcp:latest
Or build it yourself with docker build -t mcp-helloasso ..
The server is listed in the official MCP registry as
io.github.edouard-claude/helloasso-mcp, so registry-aware clients offer it directly
with the variables to fill in.
The image runs unprivileged and speaks stdio by default; add --http for the HTTP
transport. See Run it over HTTP instead.
In the HelloAsso back office of your association: My account → Integrations and API
(Mon compte → Intégrations et API). You get a client id and a client secret,
with the AccessPublicData, AccessTransactions and Checkout privileges, scoped to
your organization. Partners get theirs from HelloAsso's partnership team.
Then check the wiring before pointing a client at it:
HELLOASSO_CLIENT_ID=… HELLOASSO_CLIENT_SECRET=… mcp-helloasso --check
environment production
base url https://api.helloasso.com/v5
toolsets organization, forms, sales, checkout, directory, partner
read only false
tools 38
token ok
reachable 1 organization(s)
Les Amis du Vélo (les-amis-du-velo) as OrganizationAdmin
organization Les Amis du Vélo (les-amis-du-velo)
ready.
| Variable | Required | Default | What it is |
|---|---|---|---|
HELLOASSO_CLIENT_ID | yes | The API client id | |
HELLOASSO_CLIENT_SECRET | yes | Its secret | |
HELLOASSO_CLIENT_SECRET_FILE | A file holding the secret instead, for Docker secrets | ||
HELLOASSO_ACCESS_TOKEN | Instead of a client: a ready-made bearer token, used as is | ||
HELLOASSO_ENVIRONMENT | production | sandbox targets api.helloasso-sandbox.com | |
HELLOASSO_ORGANIZATION | The organization slug tools default to | ||
HELLOASSO_TOOLSETS | all | organization,forms,sales,checkout,directory,partner | |
HELLOASSO_READ_ONLY | 0 | 1 withholds every write tool | |
HELLOASSO_TIMEOUT_SECS | 30 | Per-request timeout | |
HELLOASSO_MAX_RETRIES | 3 | Retries on 429 and transient 5xx | |
HELLOASSO_BASE_URL | per environment | API root override | |
HELLOASSO_TOKEN_URL | per environment | Token endpoint override | |
HELLOASSO_HTTP_BIND | Listen address for the HTTP transport | ||
HELLOASSO_HTTP_TOKEN | Bearer token the HTTP transport requires | ||
HELLOASSO_LOG | info | warn, debug, mcp_helloasso=debug… |
Which organization. An association's client reaches exactly one organization, and the
server finds it on its own. A partner client reaching several gets the list back as
candidates; set HELLOASSO_ORGANIZATION or pass organization per call. The binary
refuses to start on a malformed variable and names the one at fault.
claude mcp add helloasso --scope user \
--env HELLOASSO_CLIENT_ID=… \
--env HELLOASSO_CLIENT_SECRET=… \
-- /absolute/path/to/mcp-helloasso
claude_desktop_config.json:
{
"mcpServers": {
"helloasso": {
"command": "/absolute/path/to/mcp-helloasso",
"env": {
"HELLOASSO_CLIENT_ID": "…",
"HELLOASSO_CLIENT_SECRET": "…",
"HELLOASSO_READ_ONLY": "1"
}
}
}
}
Start read-only; lift it once you trust the setup.
Same shape: one command pointing at the binary, the credentials in env. The server
speaks the stdio transport and prints nothing but protocol on stdout — logs go to stderr.
Point it at http://127.0.0.1:8080/mcp after starting the server with
--http 127.0.0.1:8080. See below.
Then ask "which HelloAsso organization are you connected to?". whoami should answer
with your association's name.
38 tools in six toolsets, covering the 36 routes of the
public v5 spec.
HELLOASSO_TOOLSETS picks which ones are registered; HELLOASSO_READ_ONLY=1 keeps only
the ones that declare themselves read-only.
| Tool | Route | |
|---|---|---|
summarize_payments | composite | Totals over a period: collected, per form, means, month, refunds |
list_orders | GET /organizations/{org}/orders | Orders, by period, person or form type |
list_form_orders | GET …/forms/{type}/{slug}/orders | The orders of one form |
get_order | GET /orders/{id} | One order: payer, items, payments, refund eligibility |
cancel_order | POST /orders/{id}/cancel | Stop future instalments — confirm: true |
list_items | GET /organizations/{org}/items | Tickets, memberships, donations: the people |
list_form_items | GET …/forms/{type}/{slug}/items | An event's attendees, a campaign's members |
get_item | GET /items/{id} | One item, with custom fields and ticket URL |
list_payments | GET /organizations/{org}/payments | Payments, by period, person or state |
list_form_payments | GET …/forms/{type}/{slug}/payments | The payments of one form |
get_payment | GET /payments/{id} | One payment, with refund operations and receipts |
refund_payment | POST /payments/{id}/refund | Full or partial refund, MFA-aware — confirm: true |
export_cash_out | GET /organizations/{org}/cash-out/{id}/export | A payout's detail, JSON, CSV or Excel |
| Tool | Route | |
|---|---|---|
list_forms | GET /organizations/{org}/forms | Every form, by state and type |
list_form_types | GET /organizations/{org}/formTypes | The types in use |
get_form | GET …/forms/{type}/{slug}/public | A form in full: tiers, prices, custom fields |
get_form_stats | GET …/forms/{type}/{slug}/stats | Participants and sales per tier |
quick_create_event | POST …/forms/Event/action/quick-create | An event with tiers, in one call |
update_form_state | PUT …/forms/{type}/{slug}/state | Publish, hide, disable — deleting needs confirm: true |
list_activity_types | GET /values/form/{type}/types | Subtypes for a form type |
| Tool | Route | |
|---|---|---|
create_checkout_intent | POST /organizations/{org}/checkout-intents | A payment link for an amount, in one go or instalments |
get_checkout_intent | GET /organizations/{org}/checkout-intents/{id} | Its outcome, and the order once paid |
| Tool | Route | |
|---|---|---|
whoami | composite | Environment, client, default organization and its profile |
get_organization | GET /organizations/{org} | An organization's public profile |
list_my_organizations | GET /users/me/organizations | The organizations this client holds a role on |
list_organization_categories | GET /values/organization/categories | Reference list |
list_company_legal_statuses | GET /values/company-legal-status | Reference list |
| Tool | Route | |
|---|---|---|
search_organizations | POST /directory/organizations | By name, place, category, tag |
search_forms | POST /directory/forms | By text, place, dates, type, price, tag |
get_tag | GET /tags/{name} | One tag, with usage count and amount |
list_public_tags | GET /values/tags | Every public tag |
| Tool | Route | |
|---|---|---|
get_partner | GET /partners/me | The partner account |
list_partner_organizations | GET /partners/me/organizations | Organizations that authorized the partner |
update_partner_domain | PUT /partners/me/api-clients | The OAuth redirect domain |
set_notification_url | PUT /partners/me/api-notifications | The webhook URL, main or per event type |
delete_notification_url | DELETE /partners/me/api-notifications | Remove it — confirm: true |
set_organization_notification_url | PUT /partners/me/api-notifications/organizations/{org} | Per-organization webhook |
delete_organization_notification_url | DELETE /partners/me/api-notifications/organizations/{org} | Remove it — confirm: true |
Three resources and two templates, for clients that prefer attaching context to calling a function:
| URI | |
|---|---|
helloasso://organization | The default organization's profile |
helloasso://forms | Its forms |
helloasso://my-organizations | Every organization the client reaches |
helloasso://forms/{form_type}/{form_slug} | One form in full |
helloasso://orders/{order_id} | One order in full |
Four prompts drive the workflows associations actually run:
treasury_report — totals, per-campaign split, means, refunds and what stands
out, in French number formatting. Writes nothing.find_person — everything one person did: memberships, tickets, payments,
what is pending. Writes nothing.attendee_list — every participant of a form with their answers, walked to the
last page and checked against the form's statistics. Writes nothing.refund_request — find the payment, check eligibility, propose, stop and
wait, then refund and read it back.Everything below was checked against the live API, is handled for you, and is written down because the next person integrating HelloAsso will hit the same walls.
| What happens | What this server does |
|---|---|
Access tokens last 30 minutes, and HelloAsso requires renewing them with the refresh_token rather than a new grant | Renews a minute early, refresh first, new grant only if the refresh is refused |
| A token can be revoked before it expires | A 401 drops the token and retries once with a fresh one |
| Amounts are integers in cents everywhere | Said in the server instructions and every tool description; summarize_payments adds euros |
A form is addressed by (organization, type, slug) | Every form tool also takes the public URL, and French type names |
Date filters are half-open: to is exclusive | Periods (2026-09, last month) are converted to the right [from, to) |
Arrays in query strings are repeated keys (states=A&states=B) | Built that way, never comma-joined |
Errors come in three envelopes: HelloAsso's errors[], ASP.NET's validation map, OAuth's error | All three read into one message with the code in the error's data |
A 403 says only insufficient Privileges | Names the privilege the spec requires for that route, and who grants it |
An association's own client cannot call the directory, tag, activity-type or partner routes (FormOpenDirectory, OrganizationOpenDirectory, FormAdministration, partner client) | Those tools stay registered for partners; with an association client they answer which privilege is missing |
Most listings (orders, items, payments, directory) report -1 for totals and pages, and paginate by continuation token | -1 is dropped rather than shown as a count; the token is surfaced, and summarize_payments walks it to the end |
| Refunds are protected by strong authentication | The MFA headers are exposed as optional arguments; the error says which one is needed |
quick-create only supports events | Exposed as quick_create_event, which says so |
The cash-out export picks JSON, CSV or Excel from the Accept header, and its CSV starts with a byte-order mark | export_cash_out takes a format and strips the BOM |
Its CDN refuses requests without a User-Agent | Every request carries mcp-helloasso/<version> |
| 429 and transient 5xx | Retried with backoff, honouring Retry-After |
Reading a treasury is sensitive; changing it is more so. The defaults lean careful.
refund_payment, cancel_order, deleting a form and
removing a webhook refuse without confirm: true. A refused refund reads the payment
first and says "payment 67890 (25.00 € by Camille Martin, Authorized) would be
refunded in full", so the person confirms what they actually see. All four are
annotated destructiveHint.HELLOASSO_READ_ONLY=1 drops every route that does not
annotate itself readOnlyHint — not a list of names to keep in sync, so a new write
tool is excluded by construction. A test asserts it.https://, ids must be positive.Debug, sent only to the token
endpoint, and never serialized into a tool answer or a log line; whoami shows the
first eight characters of the client id and nothing of the secret.export_cash_out's save_to is refused when the server
is reached over HTTP.mcp-helloasso --http 127.0.0.1:8080
# MCP at http://127.0.0.1:8080/mcp, health at /healthz
Off loopback, the server refuses to start without HELLOASSO_HTTP_TOKEN: one API
client means one identity, and an open endpoint is an open door to an association's
payments. With the token set, every request to /mcp needs
Authorization: Bearer <token>; /healthz stays open for probes. All sessions share one
OAuth token.
docker run -d --name mcp-helloasso -p 8080:8080 \
-e HELLOASSO_CLIENT_ID=… \
-e HELLOASSO_CLIENT_SECRET=… \
-e HELLOASSO_HTTP_BIND=0.0.0.0:8080 \
-e HELLOASSO_HTTP_TOKEN="$(openssl rand -hex 32)" \
mcp-helloasso
Put TLS in front of it. The process speaks plain HTTP and does not pretend otherwise.
cargo test # 79 tests
cargo clippy --all-targets -- -D warnings
cargo fmt --all --check
mcp-helloasso --list-tools # the surface, as registered
mcp-helloasso --check # the configuration, against the live API
The suite includes the protocol itself: tests/mcp_stdio.rs spawns the real binary,
speaks JSON-RPC to its stdin and asserts on what comes back — the handshake, all 38 tools
with their schemas, a form named by URL, a period turned into dates, a summary that only
counts collected money, a refund refused without confirmation and sent with its MFA
header with it, read-only mode. tests/api_contract.rs holds the token lifecycle, the
retries and the error envelopes against a fake HelloAsso, and tests/mcp_http.rs checks
the HTTP transport and its bearer guard.
src/
├── client.rs REST over HTTP: OAuth2 lifecycle, retries, the three error envelopes
├── config.rs the environment, validated once, with the secret redacted
├── resolve.rs URLs, slugs and form types, and the refusal to guess
├── dates.rs today, last month, 2026-09, last 30 days
├── tools/ one module per toolset, one function per route
├── resources.rs three URIs and two templates
├── prompts.rs four workflows that ask before touching money
└── server.rs capabilities, instructions, and the glue
Bump version in Cargo.toml, add its section to CHANGELOG.md, then
push a tag:
git tag v0.1.1 && git push origin v0.1.1
The release workflow checks the tag against Cargo.toml and the changelog, builds the
five binaries with their checksums, pushes ghcr.io/edouard-claude/helloasso-mcp, and
publishes the GitHub release with the changelog section as notes.
Issues and pull requests welcome. A change is ready when cargo test,
cargo clippy --all-targets -- -D warnings and cargo fmt --all --check pass, and when a
new tool carries a description, an annotation and an output schema — three tests will
tell you if it does not.
MIT — see LICENSE. Not affiliated with HelloAsso; HelloAsso is a trademark of its owner.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
docker run -i --rm ghcr.io/edouard-claude/helloasso-mcp:0.1.1Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-edouard-claude-helloasso-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/edouard-claude/helloasso-mcp:0.1.1"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceghcr.io/edouard-claude/helloasso-mcp:0.1.1dockerHelloAsso works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.