# auth.md — Authentication at Muutto365

<!-- The H1 leads with the filename because that is the convention consumers
     look for: isitagentready.com's authMd check reads the file, finds a valid
     200 and text/markdown, then fails it with "No H1 heading containing
     'auth.md' found". Costs nothing and loses no meaning, so it matches. -->


Short version: **most of what an agent would want to do here needs no
authentication at all.** Pricing, availability and booking are anonymous for
humans, so they are anonymous for agents too. Only the MCP server requires a
token, and only because it has no browser session to belong to.

## Which interface needs what

| Interface | Endpoint | Auth |
| --- | --- | --- |
| WebMCP (in-page tools) | `https://muutto365.fi/varaa-muutto/` | None. Runs in the visitor's own session. |
| REST API | `https://api.muutto365.fi` | None. No API key. Rate limited per endpoint. |
| MCP server | `https://mcp.muutto365.fi/mcp` | OAuth 2.1, authorization code + PKCE. |
| Cancellation | `https://muutto365.fi/cancel.html?token=…` | The cancellation token from the confirmation email. |

If a capability is available anonymously to a person, do not mint an identity
to use it. The MCP server exists for callers with no browser tab, not as a
privileged path around the public flow — it exposes a *subset* of what the
public interfaces do, not a superset.

## OAuth 2.1 on the MCP server

The authorization server is [Origo](https://github.com/ieepirzy/origo).

- **Issuer:** `https://mcp.muutto365.fi`
- **Authorization server metadata:** `https://mcp.muutto365.fi/.well-known/oauth-authorization-server` (RFC 8414)
- **Protected resource metadata:** `https://mcp.muutto365.fi/.well-known/oauth-protected-resource` (RFC 9728)
- **Dynamic client registration:** `https://mcp.muutto365.fi/register` (RFC 7591), open, no pre-provisioned credentials
- **Grant:** `authorization_code` + PKCE (`S256`), plus `refresh_token`
- **Client auth:** register with `token_endpoint_auth_method: "none"` for a secretless public client

Flow: register → authorize with PKCE → exchange the code at `/token` → call
`/mcp` with `Authorization: Bearer …`.

### About the copies of this metadata on `muutto365.fi`

`https://muutto365.fi/.well-known/oauth-authorization-server`,
`/.well-known/openid-configuration` and `/.well-known/oauth-protected-resource`
are served from the website origin as **discovery aids**, so that an agent that
starts at the apex domain can find the authorization server without knowing to
look at the `mcp.` host first.

They are not a second authorization server. `muutto365.fi` issues no tokens and
has no signing keys. Both documents name `https://mcp.muutto365.fi` as the
issuer, and that value — not the host you fetched the document from — is the
one to use. Fetch the authoritative copies from the issuer before relying on
them for anything beyond discovery.

## Cancellation tokens

The confirmation email contains a cancellation link carrying a per-booking
token. That token is the entire authorization: it identifies one booking and
permits cancelling it. There is no endpoint that addresses a booking without
it, no lookup by email or phone, and no agent-specific bypass.

An agent that can read the customer's mailbox can follow the link, and the
cancellation page registers WebMCP tools scoped to that one booking. That is
the intended path — the same one a human takes.

## What is not exposed

- Administrative capability, in any interface. The admin surface is a separate
  application behind its own authentication and is not reachable from here.
- Internal notes, pricing coefficients, fraud signals, and other operational
  fields, in any response.
- Booking creation without card capture. Creating a booking requires a card on
  file (stored via Stripe, not charged). That is an anti-fraud control on an
  otherwise anonymous flow, and "the caller is an agent" does not waive it.

## Contact

`asiakaspalvelu@muutto365.fi` — including for anything about this document that
turns out to be wrong or out of date.
