Swiftaid 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., "@Swiftaid MCP serveris the Swiftaid sandbox up and do our credentials still work?"
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.
Swiftaid MCP server
An MCP server that lets Claude, ChatGPT and other MCP clients work with the Swiftaid Gift Aid API from the side of a donation platform integrated with Swiftaid: the API's health, a charity's on-boarding status, the Gift Aid claims Swiftaid has made for a charity, a donor's Swiftaid authorisation, the Gift Aid declaration behind a donation, and (when enabled) registering enduring declarations and filing donations. It is built from Swiftaid's public developer documentation: the OpenAPI 3.0.3 spec "Swiftaid API" 1.3.1 at static.swiftaid.co.uk/apis/openapi/external/v1/api.yaml and the Getting Started, Reference and Go to production pages at developers.swiftaid.co.uk.
Once it's connected, someone at the platform can ask things like:
"Is the Swiftaid sandbox up, and do our credentials still work?"
"Has the charity SA12345 warranted our donations yet?"
"How much Gift Aid has Swiftaid claimed for SA12345 since January, and is the September claim filed with HMRC?"
"Does the donor with jo.example@example.com have an active Swiftaid authorisation this tax year?"
"Was Gift Aid declared on donation stl_123456, or has it been reversed?"
With writes enabled: "File these three settled donations to SA12345." / "Register this enduring declaration we took over the phone."
Tools
Tool | What it does | API calls |
| Whether the API answers (no token needed) and, by default, whether the auth service issues a token for the configured credentials, environment and scopes. |
|
| Whether a charity, by HMRC customer id, has warranted donations from your platform as eligible for Gift Aid. |
|
| A charity's Gift Aid claims, newest first. Optional date range, applied locally. With |
|
| One claim: created, invoiced and filed dates, whether it is filed with HMRC, donation count and total, Gift Aid due, overclaim, the donation ids. The HMRC filing receipt (XML) only on request. |
|
| Whether a donor has an active Gift Aid intermediary authorisation, by Swiftaid donor id, or by email or UK mobile number looked up to the id first. The API returns no names or contact details here. |
|
| The Gift Aid declaration for one donation: status (declared or reversed), amount, date, the nominee's charity reference, retrospective matching, the donor's name. The donor's address and postcode only on request. |
|
| Registers 1 to 25 enduring Gift Aid declarations. Refuses locally names with digits, a malformed postcode or HMRC id, and repeated ids. Writes only. |
|
| Files 1 to 25 donations for Gift Aid processing, identifying the donor by donor id, email, UK mobile, match id or declaration id and the charity by HMRC id, direct reference or nominee. Says which donations have no settlement date yet. Writes only. |
|
The spec has no endpoint that lists charities, donations or donors, so there are no such tools. Not covered on purpose: POST /donors and POST /donors/{donorId}/authorisation (creating donor accounts and authorisations: these carry the donor's name, address and, for card accounts, PAR, BIN, last four digits and expiry), POST /donors/{donorId}/accounts (linking card, email and phone accounts), POST /donors/match and the experimental POST /donors/matches (matching on email, phone, card data and address), PATCH /declarations/{declarationId}, DELETE /declarations/{declarationId} (which reverses any Gift Aid claimed), PATCH /donations (settlement dates) and DELETE /donations/{donationId} (cancelling Gift Aid on a donation). create_donation does not accept the card-based identifiers (par donors, terminal transactions) or the name-and-address donor, which the Reference page reserves for data-processor use. The test suite asserts that no endpoint other than the ones in the table is called.
Related MCP server: mcp-african-markets
Setup
Requires Node 18 or later.
npm install
npm run buildYou need client credentials from Swiftaid. Sandbox credentials are issued on request by Swiftaid's developer support (dev@swiftaid.co.uk) after you accept the API terms; production credentials are issued by Swiftaid's engineering team once a partnership agreement is signed (Go to production page). The server exchanges them for an access token as the Getting Started page documents: POST https://auth.streeva.com/oauth2/token with Authorization: Basic base64(client_id:client_secret) and a form body grant_type=client_credentials, audience=<the API address for your environment> and scope=<space-separated scopes>. By default it asks for read:charity read:claim read:donor read:declaration, plus create:declaration create:donation only when writes are enabled.
Claude Desktop: add this to claude_desktop_config.json:
{
"mcpServers": {
"swiftaid": {
"command": "node",
"args": ["/absolute/path/to/swiftaid-mcp/dist/index.js"],
"env": { "SWIFTAID_CLIENT_ID": "your-client-id", "SWIFTAID_CLIENT_SECRET": "your-client-secret" }
}
}
}Claude Code:
claude mcp add swiftaid -e SWIFTAID_CLIENT_ID=your-client-id -e SWIFTAID_CLIENT_SECRET=your-client-secret -- node /absolute/path/to/swiftaid-mcp/dist/index.jsVariable | Required | Meaning |
| yes | Your client ID, sent in the token request's HTTP Basic header. |
| yes | Your client secret, sent in the same header. Never logged or included in an error message. |
| no |
|
| no | Space-separated scopes to request instead of the defaults, for a client that has not been granted all of them, e.g. |
| no |
|
| no | Overrides the API address (the token audience still follows |
| no | Overrides the token endpoint. Used by the tests. |
| no | Seconds a tool call may spend before it stops retrying (default 45, see Safety defaults). Lowered by the tests. |
Safety defaults
Read-only unless
SWIFTAID_ALLOW_WRITES=true, and onlyread:scopes are requested on the token unless it is. Read tools carry the MCPreadOnlyHintannotation. The two write tools create records and are not marked destructive; nothing that deletes, reverses or cancels is implemented.Donor names are returned. A donor's address lines, city, county and postcode are only returned when the assistant explicitly asks (
include_contact_detailsonget_declaration). In free text (the nominee's charity reference, donor names, theinfoand problem details of write results, and Swiftaid's error messages) email addresses are replaced with[email redacted], phone-number-like sequences with[phone redacted]and UK postcodes with[postcode redacted]by default. These are heuristic patterns: the email and phone ones are the same as this author's Signable and Carebit servers (UK and international phone shapes); postcodes are matched in any letter case and with any spacing (GU1 3RT,gu1 3rt,GU1 3RT), as the spec's own postcode pattern allows, which can also catch a short word pair such asa1 2nd. Ids such asclm_123abcandstl_123456and HMRC ids such asSA12345are left alone.get_donornever echoes the email or phone number it looked up.Card and payment data are never returned. None of the documented read responses carries any, every formatter copies only the documented fields it names, and a card number typed into free text (13 to 19 digits that pass the Luhn check) is replaced with
[card number redacted]even when contact details were requested. No file is ever downloaded; the HMRC filing receipt is an XML string in the claim response and is only returned on request.The access token is held in memory only, refreshed a minute before its
expires_inruns out (the documented example is 86,400 seconds; a token shorter than two minutes is refreshed after half its life), and fetched afresh once when a call answers 401. The token and the client secret are never logged or put in a tool result; should Swiftaid ever echo either (or the encoded Basic credentials) in a message, the exact value is replaced with[redacted], whatever its length.IDs are checked before any call is made: HMRC customer ids against the spec's pattern
^(?:X|[A-Z]{2})\d{1,5}$; claim and donor ids must be up to 64 letters, digits,_and-, and donation ids up to 50 (the spec'smaxLengthfordonationId), because the spec types them as plain strings. The same rule applies to ids that come back from the API before they are put in a path (a claim id from the claims list, a donor id from the lookup), so a value such as..never reaches another endpoint. Phone numbers must match the UK-mobile pattern the spec uses for donor phone numbers (the lookup'svalueparameter itself has no pattern). Date filters must be real calendar dates, andcreated_fromno later thancreated_to. Write bodies are checked against the spec's rules before posting: no digits in names,lastnameat least 2 characters, the postcode pattern,sourceRefup to 250 characters, declaration ids up to 100, a net amount no larger than the gross, dates asYYYY-MM-DDor date-times withZor an offset.Rate limits: Swiftaid documents none, and its spec documents no 429 response and no
Retry-After. Requests are spaced 250 ms apart as a polite guess. A 429 is still retried at most twice for any method, including bothPOSTs and the token request, on the assumption that a rate-limited request was not processed, waiting forRetry-After(whole or fractional seconds, or an HTTP-date; 2 s without it, and 4 s before a third attempt, which the tests do not exercise). ARetry-Afterlonger than 10 seconds makes that request give up at once with the wait in the message, so one request waits at most about 20 seconds. A tool call can make several requests (a token request,get_donor's lookup and authorisation check,list_charity_claims' report fetches), so each tool call also has a 45-second budget (SWIFTAID_TOOL_BUDGET_S): a retry whose wait would end after it is not attempted, and the request fails saying so. 45 seconds is chosen to leave room for the last request under the MCP client's default 60-second timeout; the tests check the mechanism with a 4-second budget, not the 60-second outcome.list_charity_claimswithinclude_totalsthen still returns the claims list and the totals read so far, and names the claims it did not add up.502, 503 and 504 are retried the same way for
GETand for the token request only. APOST /declarationsorPOST /donationsis never retried after a gateway error, because it may already have been processed; the error says to send it again with the same ids, which Swiftaid reports asduplicateif it already holds them (as the spec's response examples show). AGETthat fails three times says the service may be unavailable, without the gateway's HTML.A 200 whose body is not JSON (a proxy or a login page in the way) is reported as an error naming
SWIFTAID_ENV/SWIFTAID_BASE_URL, never as an empty result. A write answered 200 with something other than the documented array of per-item results is an error saying the outcome is unknown and to resend with the same ids, never "0 accepted".Rejected client credentials produce a message naming the variables to check and the environment; a token refused by the API even after a refresh says to check that
SWIFTAID_ENVmatches the credentials; a 403 names the scope the operation needs (from the spec'ssecurityblock); a 404 says the record does not exist for your client, with a specific explanation for donor lookups and declarations; a 400 or 409 passes on Swiftaid's problem details (title,detail,errors), redacted.
Tests
npm testThe test suite (35 checks, 116 requests, about 40 seconds):
Validates every fixture record against the component schemas in Swiftaid's published spec (
CharityResponse,ClaimIndex,ClaimSummary,ClaimReport,GiftAidDeclaration,UserState,Authorisation) with Ajv 2020 and ajv-formats, including negative controls. The spec'soneOf+discriminatorschemas (DonorIdentifier,TransactionIdentifier,Account) are resolved the way the discriminator mapping says, because their branches carry noconstontypeand a plainoneOfwould match several of them; a check proves the resolution rejects a bad phone value, a missing value and an unknown type. The spec is downloaded tospec.yamlon the first run if it is missing.Starts a local mock of the sandbox API under
/integrations/v1and of the token endpoint, and checks its answers against the documentation: the token response has exactly the three documented keys; the record and list responses validate against each operation's documented response schema; the documented 401 and 404 of theGEToperations have no body (the spec defines none);GET /healthcheckneeds no token; the write responses validate againstBatchDeclarationResponseandDonationsResponse, with a repeated id answeredduplicateas in the spec's examples; and the 400BadRequestDetailedexample is served verbatim. That example contradicts the spec's ownProblemDetailsschema in exactly one place, which the check asserts:statusis typed as a string and given as a number.Starts the built server and drives it over stdio with the official MCP client, 33 checks: tool list and annotations; the token request (HTTP Basic computed at runtime, form body with exactly
grant_type,audienceandscope, read scopes only unless writes are on, the scope list fromSWIFTAID_SCOPEwhen set), the token reused across calls, replaced once after a 401 and refreshed before a shortexpires_inruns out; the token and the client secret scrubbed from error messages that echo them;GET /healthcheckwithout a token; every read tool; the claims list fetched in one request with no query parameters (the spec documents no pagination and no filters) and sorted, filtered and totalled locally, with impossible dates and an inverted range refused;include_totalscapped at 12 report requests with a note when more claims are listed, ids such as..and""from the API never used in a path, a report answering 404 named intotals_errorswhile the list and the other totals are kept;typeandvaluepassed toGET /donors/exactly as documented for email and phone lookups; donor names returned and address and postcode withheld by default and returned on request; emails, phone numbers (UK and+44forms) and postcodes (upper, lower and mixed case, and with two spaces) redacted from the nominee reference, donor names, write results and error messages by default, with HMRC and claim ids left alone, and names returned as stored on request; card fields injected into a response (undocumented, for this check only) and a Luhn-valid card number never returned, even with contact details requested; both write bodies validated against the spec's request schemas (EnduringDeclarations,Donationsand each donor and transaction branch); the write gate with the variable unset and set tofalse; local refusal of invalid write content and invalid ids with no request made; clear 404 messages; a 403 naming the missing scope;invalid_scopefrom the token endpoint; wrong credentials reported without echoing the secret;SWIFTAID_ENV=productionrequesting the production audience and the resulting 401 explained; the 429 retry waiting forRetry-Afterin the seconds, fractional-seconds and HTTP-date forms and 2 s without the header, giving up at once above the cap (the message naming the API path) and after three attempts on a persistent 429; a 429 onPOST /donationsretried once and the donation filed once; a 429 then a 503 on the token request retried; a tool call giving up at its time budget (lowered to 4 s for the check) withinclude_totalskeeping the list and naming the claims not added up; a 502 retried forGETand never for eitherPOST; a write answered 200 without the documented array reported as an unknown outcome; three 503s reported with advice and without HTML; a non-JSON 200 reported as an error; a 400 and the documented 409 with problem details passed on redacted; and that every request either went to the token endpoint with Basic auth and no credentials in the body or query, or went toGET /healthcheckwithout auth, or carried a Bearer token the mock had issued, to a documented method and path, with the set of endpoints used asserted exactly.
Status
This is a working prototype. It has not yet been run against the live API or the sandbox, because it was built without Swiftaid credentials (sandbox credentials are issued on request, not self-serve). Everything below is taken from the published spec and documentation and should be confirmed on a sandbox account:
The token endpoint: that the form body with
audienceandscopeand the Basic header are accepted exactly as the Getting Started page shows; whether the response carries anything beyondaccess_token,token_typeandexpires_in; and the status and body for wrong credentials and for a scope the client has not been granted. The mock answers OAuth 2.0's401 invalid_clientand400 invalid_scope; Swiftaid documents neither.Which scopes a sandbox client is granted, and whether requesting a scope it lacks fails the token request (as the mock does) or silently narrows the token.
Whether a token issued for one environment's audience is refused by the other environment with a 401 (as the mock does).
The bodies of the error responses. The spec defines no content for the
GEToperations' 400, 401, 403 and 404 or forPOST /donations' 400; the mock sends empty bodies, and the server's messages do not depend on a body.GET /charities/{hmrcCustomerId}for a charity Swiftaid does not know: the spec documents 200, 400, 401, 403 and 500 but no 404. The mock answers 404; the real API may answer 200 withwarranted: falseor a 400.GET /charities/{hmrcCustomerId}/claimsfor a charity with many claims: the spec documents no pagination, so the server assumes the whole list arrives in one response. The sort order is not documented; the server sorts bycreatedDate, newest first.The unit of the claim amounts (
totalDonations,totalGiftAid,overclaimAmount) and of the declarationamount: the donation schema and the Reference page say amounts are in pence (500 = £5.00); the claim and declaration schemas do not restate it, and the tools return the integers as given with that note.GET /donors/?type=phoneNumber: whether the lookup matches a number written differently from the stored one (07700 900456against+447700900456); the server sends the value as given. The spec's path has a trailing slash (/donors/), which is what the server calls; the Getting Started example calls/donorswithout it.GET /donations/{donationId}/declaration: that reading declarations has to be enabled for the platform (the Reference page says so), what the API answers when it is not (the server's message assumes a 404), and thedonatedOnformat: the Reference page's example (2018-07-17T10:02:34) has no time zone, which the spec's ownformat: date-timedoes not allow. The server passes the value through as returned.POST /declarations: the body follows the spec'sEnduringDeclarationschema (source: { sourceRef, requestDate }); the Reference page's example uses a different, flat shape (sourceRefandcreatedDateat the top level). Which one the API accepts must be checked. Also whetherstartDateandrequestDateaccept a bare midnight UTC time as the server sends for aYYYY-MM-DDinput, and how an item rejected for a missing warrant is reported.POST /donations: that the per-item results come back as documented (accepted,info,duplicatefor a known id), whether a repeated POST after a lost response is really answeredduplicaterather than filed twice (the server's advice after a gateway error relies on it), and the nomineecreateDeclaration/fileDeclarationbehaviour.A 429 on either
POSTis retried on the assumption that a rate-limited request was not processed; Swiftaid documents no 429 at all.The HMRC customer id format. The server checks the spec's pattern
^(?:X|[A-Z]{2})\d{1,5}$(X or two capital letters, then 1 to 5 digits), but the Reference page says the customer number is "1 or 2 letters followed by up to 5 numbers". If the Reference page is right, the spec's pattern (and so this server) refuses real ids with a single letter other than X.What
GET /healthcheckreturns: the spec documents a 200 with no body and no security requirement; the server sends no token and reports the status and the first 200 characters of any body.How many requests per second the API tolerates; nothing is documented, so the 250 ms spacing is a guess.
Going to production
This version runs locally over stdio, with the platform's own client credentials. For Swiftaid's partners to connect from claude.ai or ChatGPT without handling credentials, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by Swiftaid, and then a listing in the Claude and ChatGPT connector directories. A production version, tested on the sandbox, would also cover setting settlement dates (PATCH /donations), updating and ending enduring declarations, and, with Swiftaid's guidance, the donor authorisation and matching endpoints, whose bodies carry personal and card data.
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
MCP server for Codat — companies, connections, invoices, bills and financial statements.
REST-to-MCP for UK hospitality. Safety proxy: circuit-breakers, rate limits, whitelists. Apache 2.0.
A paid remote MCP for Equibles, built to return verdicts, receipts, usage logs, and audit-ready JSON
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Related MCP Servers
- AlicenseAqualityDmaintenanceQuery UK charity data via MCP, including charity details, financial history, trustees, and governing documents.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP-aware agents to initiate M-Pesa B2C/B2B payouts, query transaction status, validate phone numbers, and reconcile M-Pesa statements against ledger CSVs to surface discrepancies, plus structured lookups for county public data and KRA tax helpers. It runs in sandbox mode by default, uses idempotency keys for safe retries, and stays read-only for reconciliation and lookups.MIT
- AlicenseAqualityCmaintenanceEnables MCP clients to search availability, read services, resources, bookings and customers, and, when writes are enabled, reserve slots, create or cancel bookings, and add or update customers.10MIT
- 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