Layout Documentation

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

Which 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 user's list of connected apps.

The user 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. 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 end user 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 on every call. 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.

Scope

One scope exists today: order. The connection is all or nothing, which is what the user 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 in its own definition, and the check runs before input is even validated, so an ungranted caller cannot map our input rules by probing them. Adding a narrower scope later is a change to those declarations rather than a security rewrite.

Revocation

Revocation is a watermark on the account rather than a list of dead tokens. When a user revokes, the account records the moment, and any credential minted before it is void from that point, including tokens already sitting in a host's memory and refresh tokens issued earlier.

This is why revocation is effective on the next call rather than at the next expiry, and why revoking one assistant does not require the others to notice anything.

What the connector cannot do

  • It cannot read or change the user'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 the user did not confirm. See money controls.
  • It cannot see another user's anything. Isolation is enforced in the database, per user, on every query.

Updated August 17, 2026