Layout

How the connector works

Layout is an MCP server at https://mcp.layout.link, over Streamable HTTP. Every call carries one credential: an OAuth access token our own authorization server issued for that user and that resource.

Discovery

An unauthenticated call gets a 401 with a WWW-Authenticate challenge pointing at our protected resource metadata, per RFC 9728:

GET https://mcp.layout.link/.well-known/oauth-protected-resource

That names the authorization server, https://api.layout.link, whose own metadata is published per RFC 8414:

GET https://api.layout.link/.well-known/oauth-authorization-server

It declares what we support and nothing more: response type code, grants authorization_code and refresh_token, and S256 as the only code challenge method.

Hosts register dynamically, per RFC 7591. Registration returns a client id and no client secret, because PKCE is the protection and a secret shipped to a public client is not one.

A client's self chosen name is treated as attacker controlled, because it is: anyone can register. We map a connection to a known assistant by the redirect host where the authorization code is actually delivered, which is validated at registration and exact matched at authorize time. A client we have not verified never renders under somebody else's brand in a person's list of connected apps.

The person then signs in with their phone and sees one approval screen naming what the connection can do. Approving it mints the grant.

The token

The access token is an RS256 JWT signed by our authorization server, and it lives ten minutes; the host renews it with a refresh token. On every call we verify:

signaturerequired

Against our published JWKS at https://api.layout.link/.well-known/jwks.json.

issmust equal https://api.layout.link

The token has to have come from our authorization server.

audmust include https://mcp.layout.link

Audience binding. A token minted for any other resource is refused here, so a token leaked from another surface cannot be replayed against this one.

subthe user

The person this call acts for. It is a claim inside a token we signed, so it can never be asserted by the caller.

scopenarrowed to what we enforce

A token naming only scopes we have no rule for grants nothing and is refused at the door rather than opening a session every tool would then have to turn away.

iatwhen it was minted

Compared against the account's revocation watermark. A token with no iat is treated as issued at the epoch, so it fails closed rather than sailing past the check.

Anything missing, expired, wrongly signed, or issued for another audience is an authentication failure. There is no fallback path and no header a caller can set to identify itself.

The host's token is never passed on to our internal API. After the checks above, the connector mints its own short session for that one person and uses that.

Scope

One scope exists today: order. The connection is all or nothing, which is what the person approves on the consent screen, and we say so rather than implying a granular permission model we do not have.

The enforcement point is built anyway. Every tool declares the scope it requires, and the check runs before input is validated, so an ungranted caller cannot map our input rules by probing them. A tool that declares no scope is refused rather than guessed at.

Revocation

There are two ways to end access, and both reach a token already sitting in a host's memory.

  • Disconnecting one assistant or app. It withdraws that connection's grant and its refresh tokens. Every call checks that the grant is still live, so the next call is refused. If that check cannot be read, the token keeps the rest of its ten minute life rather than every caller being refused.
  • Signing out everywhere. It moves a watermark on the account. Any credential minted before that moment is void: the connector checks the watermark, cached for at most thirty seconds, before it mints a session for a call, and the authorization server refuses a refresh token issued before it. If the watermark cannot be read, the call is refused.

People do both at account.layout.link and in the iPhone app.

What the connector cannot do

  • It cannot read or change the person's daily spending limit.
  • It cannot read a card number or a card token, because neither is on this surface.
  • It cannot place an order without a confirm that matches the stored cart and total. See money controls.
  • It cannot see another user's anything. Isolation is enforced in the database, per user, on every query.

Updated October 21, 2026