Authentication
How the connector authenticates: discovery, dynamic registration, PKCE, the resource binding, token lifetimes and revoking a connection.
The MCP endpoint is protected by an OAuth 2.1 authorization server that Sign Customiser runs for this one resource. A client discovers it, registers itself, sends the merchant through consent, and receives a token bound to one store. Nothing here is shared with the public partner API: /api/v3 authenticates with store API keys, and no key ever reaches the MCP endpoint.
This guide is for people building or debugging a client. If you only want to connect one, read Connect an assistant.
Discovery
An unauthenticated call to the endpoint answers 401 with a challenge that names where to look:
WWW-Authenticate: Bearer resource_metadata="https://web.signcustomiser.com/.well-known/oauth-protected-resource/mcp"That pointer is the whole of client discovery. Two documents follow from it.
Protected-resource metadata (RFC 9728) is served at both /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp; clients differ over which they try, and both answer the same document. It names the exact resource, this application as its authorization server, and every scope that can be requested.
Authorization-server metadata (RFC 8414) is served at /.well-known/oauth-authorization-server and /.well-known/oauth-authorization-server/mcp. Every value in it is something the server enforces:
| Field | Value |
|---|---|
issuer | https://web.signcustomiser.com |
authorization_endpoint | https://web.signcustomiser.com/oauth/authorize |
token_endpoint | https://web.signcustomiser.com/oauth/token |
registration_endpoint | https://web.signcustomiser.com/oauth/register |
revocation_endpoint | https://web.signcustomiser.com/oauth/revoke |
grant_types_supported | authorization_code, refresh_token |
code_challenge_methods_supported | S256 |
token_endpoint_auth_methods_supported | none |
authorization_response_iss_parameter_supported is false, and it is honest: the authorization response carries no RFC 9207 iss parameter, so a client takes its issuer assurance from discovery rather than from the redirect.
Dynamic client registration
POST /oauth/register implements RFC 7591, so a client that has never met this server can register itself and start an authorization request immediately. Nothing authenticates that request, which is why it is bounded:
- Public clients only. No secret is issued and none is accepted, so a registration is worth nothing on its own.
- Redirect URIs must be HTTPS, or loopback HTTP. No fragment, no credentials in the authority, no wildcards, no other schemes. The authorization request is then matched against the registered list exactly.
- Authorization code and refresh only, response type
codeonly. - Per-IP quotas, hourly and daily, spent on every attempt including the ones that fail validation.
- Unused registrations are reclaimed. A registration that never obtained a token, or whose last use is old, is pruned.
Registering grants nothing. A merchant still has to approve scopes, and the consent screen marks a self-registered client as unverified.
PKCE
PKCE is mandatory and S256 is the only accepted method. A request with plain, or with no code_challenge at all, is refused before the authorization server sees it, and the refusal is answered directly rather than redirected: a malformed PKCE parameter means the request cannot be trusted to say where its answer should go.
A failed exchange burns the authorization code. Present the wrong code_verifier, or none at all, and the code is revoked before the error comes back, so a retry with the right verifier is refused with invalid_grant too. Start a fresh authorization request rather than re-presenting the code.
Resource binding
Send the RFC 8707 resource parameter on the authorization request:
resource=https://web.signcustomiser.com/mcpNaming another API is refused with invalid_target. Omitting it means the canonical resource, which is the only resource this server has.
The binding is recorded on the token itself, not only in the JWT audience claim, and the resource server rejects any token whose resource is not its own. It survives refresh rotation: a rotated token carries the original store and resource forward, so a client cannot change which store it acts for by refreshing, even if the same person has since authorised the same client for a different store.
What a token carries
A token is issued for one client, on behalf of one person, for one store, with the scopes that person approved. There is no wildcard scope. Three things are checked on every single request, not at connection time:
- the signature and the row expiry, so shortening a live token’s life takes effect on the next call;
- the resource binding;
- that the person who approved the connection still belongs to the store’s company. A grant dies with their access.
The store comes from the token. No request parameter can change it.
The token response does not carry a scope parameter. RFC 6749 makes it optional when the granted scopes are identical to the ones requested, which is the only case this server issues in: a merchant either approves the request as it stands or denies it, so nothing is ever quietly narrowed. A client that wants to confirm what it actually holds should call get_store, which publishes the connection’s granted scopes as scopes, rather than read them back off the token response.
Token lifetimes
| Token | Lifetime |
|---|---|
| Authorization code | 10 minutes |
| Access token | 60 minutes |
| Refresh token | 30 days, rotated on every use |
An idle connection therefore lasts 30 days. A leaked access token stops working within the hour.
Revocation
POST /oauth/revoke implements RFC 7009. Revoking an access token revokes its refresh token and the reverse, because a client asking to be forgotten means the whole grant.
Clients are public and hold no secret, so the client authentication RFC 7009 expects is the client_id the token was issued to: a caller has to both hold the token and name its client. A token this server never issued, or one already gone, answers 200 with nothing revoked, so the endpoint cannot be used to probe for live tokens.
A merchant can revoke from Tools & Settings > MCP server without the client’s cooperation. That path revokes every access token behind the connection, their refresh tokens, and any authorization code not yet exchanged.
Errors a client should handle
| Response | Meaning | What to do |
|---|---|---|
401 with a resource_metadata challenge | No token, an expired token, or one bound to another resource | Run discovery and re-authorise |
invalid_target on authorize | The resource parameter named another API | Send the canonical resource |
A tool result carrying insufficient_scope | The connection is valid; the merchant did not approve that permission | Ask the merchant to reconnect and approve the scope named in required_scopes |
429 | The endpoint’s own rate limit | Wait for retry_after |
An insufficient scope is a tool result rather than a protocol error on purpose. The connection is working, the model asked for something the merchant did not allow, and telling the model the server broke would be wrong.