Sign Customiser

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:

http
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:

FieldValue
issuerhttps://web.signcustomiser.com
authorization_endpointhttps://web.signcustomiser.com/oauth/authorize
token_endpointhttps://web.signcustomiser.com/oauth/token
registration_endpointhttps://web.signcustomiser.com/oauth/register
revocation_endpointhttps://web.signcustomiser.com/oauth/revoke
grant_types_supportedauthorization_code, refresh_token
code_challenge_methods_supportedS256
token_endpoint_auth_methods_supportednone

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 code only.
  • 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:

text
resource=https://web.signcustomiser.com/mcp

Naming 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

TokenLifetime
Authorization code10 minutes
Access token60 minutes
Refresh token30 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

ResponseMeaningWhat to do
401 with a resource_metadata challengeNo token, an expired token, or one bound to another resourceRun discovery and re-authorise
invalid_target on authorizeThe resource parameter named another APISend the canonical resource
A tool result carrying insufficient_scopeThe connection is valid; the merchant did not approve that permissionAsk the merchant to reconnect and approve the scope named in required_scopes
429The endpoint’s own rate limitWait 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.