Authentication
Every request to the MCP server (except the health check) must carry a valid MCP token. A token is bound to a single account and user, and every query the agent runs is automatically scoped to that account.
The MCP server is experimental and MCP tokens are issued on request. Contact Smily to have a token created.
Obtaining a token
MCP tokens are issued on request. When you ask for one, specify which flavor you need (see Token types & account selection below):
- a token pinned to a single account (the common case), or
- a token that can operate across several accounts, choosing the active one per request (early access — currently limited to a small number of users; see below).
A token looks like this:
mcp_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p
The plaintext token is displayed only at the moment it is created. Smily stores only a hash of
it (SHA-256) plus a short mcp_… prefix for display, and the original value can never be recovered.
If you lose it, request a new one.
Tokens may optionally have an expiry. An expired or revoked token is rejected with an
Unauthorized error.
Token types & account selection
Every token is one of two types, which differ only in how the active account for a request is determined.
| Token type | Accounts it can reach | How the active account is chosen |
|---|---|---|
| Single-account | exactly one account | Implicit — nothing to specify. |
| Multi-account | a defined set of accounts | Selected per request (see below). |
Single-account tokens
The common case. The token is bound to one account, and every tool call operates on it. You don't specify an account — it's implicit. You can ignore the rest of this section.
Multi-account tokens
Multi-account tokens are in an early-access phase and are being made available to a small number of users first. They are not yet open to everyone — broader availability will follow. If you think you need one, get in touch.
A multi-account token can operate across several accounts — the set of accounts it is authorized
for. Because more than one account is reachable, every request must say which account to act on.
A multi-account token has no default account: a tool call that doesn't pin one fails with an
Invalid params error (account_id is required for this token).
You pin the active account in one of three ways. They are tried in this order, first match wins:
-
JSON-RPC
_metaon the call'sparams, under the keybookingsync.com/account-id:{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "api_v3_list",
"arguments": { "resource": "bookings" },
"_meta": { "bookingsync.com/account-id": 1234 }
}
} -
The
account_idtool argument on atools/call(simplest for most clients — it's a normal tool argument, see Tools):{
"name": "api_v3_list",
"arguments": { "resource": "bookings", "account_id": 1234 }
} -
The
X-BookingSync-Account-IDHTTP header — a request-wide fallback applied to every call in the request when neither of the above is present:X-BookingSync-Account-ID: 1234
The value is the numeric account id. If you pin an account the token isn't authorized for, the
call fails with account_id … is not authorized for this token.
_meta and account_id are per-call, so an agent can switch accounts between calls within one
session. The header is convenient when every call in a session targets the same account. For
single-account tokens these inputs are simply ignored (and a mismatching value is rejected).
Presenting the token
The server accepts the token from three sources. When more than one is present, the highest priority wins:
| Priority | Source | How to send |
|---|---|---|
| 1 | Authorization header (preferred) | Authorization: Bearer mcp_… |
| 2 | X-MCP-Token header (legacy) | X-MCP-Token: mcp_… |
| 3 | token URL query parameter (legacy) | https://www.bookingsync.com/mcp?token=mcp_… |
Claude's custom connectors (Cowork, claude.ai, Claude Desktop, mobile) accept a URL and nothing else — there are no headers to set, and no token belongs in the URL. They obtain the token through a one-time browser sign-in and then present it exactly as row 1. See Connector sign-in below.
1. Authorization header (preferred)
This is the recommended method and what most MCP clients support natively.
curl -X POST https://www.bookingsync.com/mcp \
-H "Authorization: Bearer mcp_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'
2. X-MCP-Token header (legacy)
Still accepted for backwards compatibility; prefer Authorization: Bearer for new integrations.
curl -X POST https://www.bookingsync.com/mcp \
-H "X-MCP-Token: mcp_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p" \
-H "Content-Type: application/json" \
-d '{ ... }'
3. Token in the URL (legacy)
The server also accepts the token as a token query parameter:
https://www.bookingsync.com/mcp?token=mcp_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p
This existed for clients that could only be given a URL. Those clients now use the
connector sign-in instead, which keeps the token out of
the URL altogether — register https://www.bookingsync.com/mcp with no query parameters. The
parameter is still accepted so existing setups keep working, but a token in a URL leaks more easily
than one in a header: URLs end up in browser history, proxy logs, and crash traces. Smily redacts
the token parameter from its own application logs and error tracking; still, treat any URL that
contains a token as a secret.
Connector sign-in (for URL-only clients)
Some clients — Claude's custom connectors in Cowork, claude.ai, Claude Desktop and the
mobile apps — can only be pointed at a URL. They cannot attach an Authorization header, and they
no longer need a token in the URL either: they discover that https://www.bookingsync.com/mcp
supports an OAuth-style browser sign-in, and run it when you click Connect.
What happens:
- The client sends your browser to a Smily page titled Connect to
bookingsync-mcp. - You paste your MCP token there. Smily verifies it on the spot — a wrong, expired, or revoked token is refused on that page rather than failing silently later.
- Smily hands the client a single-use code, which it exchanges for the token behind the scenes.
- From then on the client sends
Authorization: Bearer mcp_…on every call, exactly like a header-configured client.
There is no separate connector password, client ID, or client secret to obtain: the token you paste is the credential, and it keeps its own account scoping, expiry, and revocation. Leave any OAuth Client ID / OAuth Client Secret fields in the client empty.
The click-by-click walkthrough, with a screenshot of the page, is in Connecting clients.
This is not a Smily account login. The page never asks for your Smily email or password, and completing it grants nothing beyond what the pasted token already allowed.
Access scoping
A token can only ever reach the accounts it is authorized for — one account for a single-account token, or its defined set for a multi-account token. Every tool call is scoped to the account it targets:
api_v3_list/api_v3_getonly ever return records belonging to the active account.- A multi-account token cannot reach an account outside its authorized set: pinning an unauthorized
account_idis rejected with anInvalid paramserror.
Combined with the read-only nature of the tools, this keeps the blast radius tightly bounded.
Rate limiting
Each token is limited to 1,000 requests per hour. The server returns standard rate-limit headers on every response:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The hourly request ceiling (1000). |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix timestamp when the window resets. |
Retry-After | Seconds to wait before retrying (only when throttled). |
When the limit is exceeded, the server responds with HTTP 429 Too Many Requests and a JSON-RPC
error with code -32029:
{
"jsonrpc": "2.0",
"id": null,
"error": { "code": -32029, "message": "Rate limit exceeded. Retry after 1543s." }
}
Revoking access
To cut off an agent immediately, revoke the token (or let it expire). Any active MCP session created with that token stops working on its next request, because the session re-checks the token's validity. See Protocol & transport → Sessions for session lifecycle details.
This covers connectors too: one that authenticated through the sign-in page holds that same token and nothing else, so revoking it stops the connector as well. To resume, connect again and paste a new token.