# auth.md

Life Scenario (https://lifescenario.ai) is a social and emotional learning
game for children aged 6–14. Parents have accounts; children play on the site
with a parent. This file tells an agent what it can use without an account,
and how a parent can let an agent read their own account.

## Two MCP servers

- **Public**: `https://lifescenario.ai/api/mcp` (Streamable HTTP). Anonymous
  and read-only: send no `Authorization` header; it never answers 401. Four
  tools: `list_traits`, `find_practice_situation`, `get_what_to_say`,
  `get_site_overview`. It holds no user or child data.
  - Documentation: https://lifescenario.ai/mcp
  - Server card: https://lifescenario.ai/api/mcp/server-card (with the tool
    list: https://lifescenario.ai/.well-known/mcp/server-card.json)
  - OpenAPI description of the public endpoints: https://lifescenario.ai/openapi.json
- **Account**: `https://lifescenario.ai/api/mcp/account` (Streamable HTTP).
  Read-only access to one parent's own account, after that parent allows it.
  Three tools: `get_account` (credits left; each child's `child_id`, first
  name and age), `get_child_progress` (plays, level, skill averages),
  `suggest_practice` (public practice scenarios with play links).

Agent access to the account server is switched on when
https://lifescenario.ai/.well-known/oauth-authorization-server answers 200. A
404 there means it is off: use the public server only.

## What an account connection gives, and what it never does

- Scopes: `account.read` (credits left, children's first names and ages) and
  `progress.read` (plays, last play, level, skill averages, practice
  suggestions).
- Never available to an agent: a child's answers, the feedback on them,
  model answers, safety notices, or the non-public scenario library.
- Nothing can be played, bought or changed. Scenarios are played on the site
  by the child with a parent; each scored play uses 5 credits there.
- Parent accounts only. School and teacher accounts cannot connect agents yet.
- A parent can connect up to 5 agents and disconnect any of them at
  https://lifescenario.ai/parent/privacy; the next call then gets 401.
- Each connection is limited to 30 tool calls a minute and 500 a day.

## Step 1 — Discover

Call `https://lifescenario.ai/api/mcp/account` without a token. The 401 carries:

```http
WWW-Authenticate: Bearer resource_metadata="https://lifescenario.ai/.well-known/oauth-protected-resource/api/mcp/account"
```

- Protected Resource Metadata (RFC 9728):
  https://lifescenario.ai/.well-known/oauth-protected-resource/api/mcp/account
  (and https://lifescenario.ai/.well-known/oauth-protected-resource for the site).
- Authorization Server metadata (RFC 8414), with the `agent_auth` block:
  https://lifescenario.ai/.well-known/oauth-authorization-server
- Signing keys: https://lifescenario.ai/.well-known/jwks.json

## Step 2 — Pick a method

1. **You can open a browser for the user and receive a redirect** (Claude and
   ChatGPT connectors, MCP clients): OAuth 2.1 authorization code with PKCE.
2. **You know only the parent's email**: auth.md `service_auth`.

`anonymous` registration and ID-JAG identity assertions are not accepted
(`anonymous_not_enabled`, `issuer_not_enabled`): accounts belong to parents
and are created by them at https://lifescenario.ai/sign-up. Do not create an
account or fill in a sign-in form for anyone.

The authorization server metadata also lists `identity_assertion` with
`assertion_types_supported: ["verified_email"]`. That is the v0.5 name of
`service_auth`, kept for agents and scanners that still read it, not a
second method: `{ "type": "identity_assertion", "assertion_type":
"verified_email", "assertion": "<parent email>" }` is handled exactly like
`service_auth` with that email as `login_hint`. The email is not trusted as
verified, and the same claim by the parent follows (Step 3b).

## Step 3a — OAuth 2.1 authorization code

1. Register: `POST https://lifescenario.ai/api/oauth/register` (RFC 7591) with
   `redirect_uris` and `client_name`. Public clients use
   `token_endpoint_auth_method: "none"`.
2. Send the parent to `https://lifescenario.ai/oauth/authorize` with
   `response_type=code`, `client_id`, `redirect_uri`, `code_challenge`
   (S256 only), `code_challenge_method=S256`, `state`, optional `scope`
   (`account.read progress.read`) and
   `resource=https://lifescenario.ai/api/mcp/account`.
3. The parent signs in, sees what you will read, confirms with their password
   (or an email code) and allows it. You receive `code`, `state` and `iss`.
4. Exchange at `POST https://lifescenario.ai/api/oauth/token` with
   `grant_type=authorization_code`, `code`, `redirect_uri`, `code_verifier`,
   `client_id`. Access tokens last one hour; refresh tokens 30 days and are
   rotated on every use (a reused refresh token disconnects you).

## Step 3b — auth.md service_auth

```http
POST https://lifescenario.ai/api/agent/identity
Content-Type: application/json

{ "type": "service_auth", "login_hint": "parent@example.com" }
```

The response holds `claim_token` (keep it in memory only) and a `claim` block
with `user_code`, `verification_uri`, `expires_in` (600) and `interval` (5).
Nothing is sent to that email, and the answer is the same whether or not an
account exists.

Show the parent, in one message: "Open this link, sign in, and enter this
6-digit code: 123456", with `verification_uri`. The code goes into our page,
not back to you. The parent must be signed in with the same email.

Poll `POST https://lifescenario.ai/api/oauth/token` with
`grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=…` no faster
than `interval`. Answers: `authorization_pending`, `slow_down`,
`expired_token` (ask `POST https://lifescenario.ai/api/agent/identity/claim`
with `claim_token` and the same `email` for a new code), then a token
response with `access_token` (one hour), `identity_assertion` and
`assertion_expires` (30 days). The credential is delivered once.

Exchange the assertion for a new access token when one expires:
`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=…`.

## Step 4 — Use the access token

`Authorization: Bearer <access_token>` on every request to
`https://lifescenario.ai/api/mcp/account`. A 401 on a token that worked means
the parent disconnected you or the token expired: refresh once, and if that
fails, start again.

## Revocation

`POST https://lifescenario.ai/api/oauth/revoke` (RFC 7009) with a refresh
token or an identity assertion disconnects the whole connection. Access
tokens expire within an hour and are not revoked one by one.

## Contact

Questions about agent access: https://lifescenario.ai/contact or
support@lifescenario.ai.
