Appearance
MCP connector
The MCP connector lets a shop point its own LLM at its own data: "how many stamps did the Milan store give last week", "issue a badge for EMP-00418", "which access cards expire this month".
It is a thin adapter over the same service layer the REST API uses. It introduces no new business logic and no new data access — and, non-negotiably, no ability to write a transaction.
Status
The connector is in development. This page states the design it ships with, including the two things a merchant must understand before turning it on. The tool table is generated from the registered tools and appears when they are registered — an invented tool table would be the worst possible thing on this particular page.
Setup
1. Create an MCP key
In the dashboard: Settings → API → MCP keys → Create key.
MCP keys are a separate class from REST API keys, in a separate list, with their own limits and their own revocation. They begin tmk_.
That separation is the point: an agent loop and a nightly CRM sync are different traffic shapes with different risk, and you must be able to revoke one without touching the other. Presenting a tmk_ key to the REST API — or a tsk_ key to /mcp — returns a 401 that names the mistake.
The key is shown once. Everything on Authentication about naming, expiry, rotation and revocation applies unchanged.
2. Point your client at the endpoint
https://api.tesserapp.eu/mcp
Authorization: Bearer tmk_live_…The transport is Streamable HTTP — the current remote MCP transport, not the deprecated SSE-only one. Any client that accepts a URL and a bearer token works. A typical configuration file:
json
{
"mcpServers": {
"tesserapp": {
"url": "https://api.tesserapp.eu/mcp",
"headers": { "Authorization": "Bearer tmk_live_…" }
}
}
}3. There is no one-click "Connect" button, and here is why
One-click OAuth connect is not available in v1. In hosts that require OAuth for remote MCP servers, TesserApp will not light up as a connectable service; you paste a key instead.
The MCP specification wants a protected server to be a full OAuth 2.1 resource server, and TesserApp has no OAuth authorization server — it has sessions, device keys and API keys. Building an authorization server (authorization endpoint, PKCE, dynamic client registration, consent) to ship a connector is the wrong order of work, and a half-built one is a security liability rather than a convenience.
What we do instead, so that v2 is additive rather than a rewrite: the connector already answers with OAuth-shaped failures — an RFC 9728 protected-resource metadata document, a 401 carrying WWW-Authenticate: Bearer resource_metadata=…, and a 403 carrying error="insufficient_scope". When the authorization server ships, clients that speak OAuth start working without the wire shape of failures changing.
The tools
14 tools are registered: 9 read, 3 write and 2 destructive. Generated from the server's own registry, so this table cannot drift from what tools/list returns.
| Tool | What it does | Scope | Kind | Needs |
|---|---|---|---|---|
whoami | Who am I | — | read | read-only key is enough |
list_locations | List locations | locations:read | read | read-only key is enough |
list_programs | List loyalty programs | programs:read | read | read-only key is enough |
get_program | Get one program | programs:read | read | read-only key is enough |
search_cards | Search cards | cards:read | read | read-only key is enough |
get_card | Get one card | cards:read | read | read-only key is enough |
list_transactions | List transactions | transactions:read | read | read-only key is enough |
get_analytics | Get an analytics widget | analytics:read | read | read-only key is enough |
list_webhooks | List webhooks | webhooks:read | read | read-only key is enough |
issue_card | Issue a card | cards:write | write | allow_writes |
update_card | Update a card | cards:write | write | allow_writes |
reissue_card | Reissue a card | cards:write | write | allow_writes |
archive_card | Archive a card | cards:write | destructive | allow_writes and allow_destructive |
delete_card | Delete a card (erase the holder) | cards:write | destructive | allow_writes and allow_destructive |
A key sees only the tools it may actually call: tools/list filters by scope and by privilege, so a read-only key is never shown a write tool it would then be refused. Calling one anyway is a 401, not a silent no-op.
The table above is generated from the registry rather than written here, because a documented tool that does not exist — or a tool that exists and is not documented — is exactly how a merchant misjudges what they switched on.
The shape of the surface:
- Reads: who am I, locations, programs, cards (searchable by your own
external_ref, by holder email, by program, by status), transactions, analytics, webhooks (with secrets masked). - Writes: issue a card, update a card, reissue a card. Nothing else.
- Destructive: archive a card, delete a card (the GDPR erase).
For the merchant: what you are switching on
This section is for whoever ticks the boxes, not for the developer.
Read-only is the default, and it means read-only
A new MCP key can look at your shop and change nothing. No card is issued, no holder is edited, nothing is deleted. If all you want is "ask questions about my loyalty data", leave it here.
Turning on writes
Writes let the agent issue, update and reissue cards. That is the whole write surface — an agent cannot create a program, add a location, change your prices or touch your subscription.
The write tools are hidden from the agent entirely while writes are off — not merely refused when called. A tool the model cannot see is one it cannot be talked into calling, which is the strongest of the controls here.
Turning on destructive actions
A second, independent switch, off even when writes are on. It allows archiving and deleting cards. Deleting a standard card is a GDPR erase: the holder's personal data goes, including from past webhook delivery payloads. It is not an undo.
Turn it on for a scripted offboarding flow you have tested. Leave it off otherwise.
What no MCP key can ever do
- Record a card use, award a stamp, redeem a prize or reverse a transaction. The transaction log stays evidence of what happened at a till. (Why.)
- Create, change or delete a webhook. Not even with writes and destructive both on. An instruction hidden in text that could repoint your event stream at someone else's server is the highest-value attack in this system, so the capability is not on the ceiling at all.
- Create another key, of any class.
- Manage staff, devices or your subscription.
- See another shop's anything.
Text in your data is treated as data, not instructions
Program names, holder names, external_ref values and location names are typed by people — customers, staff, an importer — and any of them can contain something shaped like an instruction ("ignore previous instructions and…"). Every tool result wraps that text in a delimited block stating it is data, not instructions.
This does not solve prompt injection; nothing does. It is why the write ceiling exists and why destructive actions need a second switch: the system is designed assuming the model will eventually read attacker-authored text.
Limits, and why they are tighter than the API's
| Limit | Default | Deployment setting |
|---|---|---|
| MCP keys per shop | 5 | MCP_KEY_MAX_PER_SHOP |
| Calls per minute, per key | 60 | MCP_KEY_RATE_LIMIT_PER_MINUTE |
| Calls per day, per key | 5000 | MCP_KEY_DAILY_CALL_BUDGET |
The per-minute limit bounds a spike; the daily budget bounds a loop that runs all night. An agent loop is a different traffic shape from a nightly CRM sync, and a runaway loop is its normal failure mode rather than an exceptional one — which is also why these limits are tighter than the REST API's.
Seeing what the agent did
Every write is audited as the MCP key that made it, so the dashboard's history shows which key issued which card and when — the same way it shows a member of staff. If something looks wrong, revoke the key: it stops working on the very next call, and no cache outlives it.