# auth.md

How an automated client authenticates to christaylor.ai. The short answer is that it does not have to.

## Audience

Agents, crawlers, and MCP or A2A clients reading this site's machine-readable surface. Humans want [the site itself](https://christaylor.ai/).

## Registration: none required

There is no registration endpoint, no API key to request, and no token to present. Every agent-facing endpoint on this origin is public and anonymous. Send the request.

Most of them are read-only. The exception is chess: playing a move creates and updates a stored game. A game is protected only by its id, which is random and unguessable, so the id is the capability, and anyone holding it can read or continue that game.

| Endpoint | Method | Authentication | What it does |
| --- | --- | --- | --- |
| `https://christaylor.ai/mcp` | POST | none | MCP over Streamable HTTP: portfolio tools, and chess |
| `https://christaylor.ai/a2a` | POST | none | A2A v1.0 JSON-RPC `message/send` |
| any page URL | GET | none | HTML, or markdown with `Accept: text/markdown` |

Supported method: anonymous HTTP. No `Authorization` header is read on any of these paths, and sending one changes nothing.

## Optional authentication

A token is never required, but it is accepted, and it buys one thing today: ownership of a chess game. A game started with a token can only be continued with that token. A game started without one is protected by its id alone.

This origin is a **resource server**, not an authorization server. Tokens are issued by Authentik at `auth.christaylor.ai`, the same identity provider behind the other products.

| Document | Location |
| --- | --- |
| Protected resource metadata (RFC 9728) | https://christaylor.ai/.well-known/oauth-protected-resource |
| Authorization server metadata | https://auth.christaylor.ai/application/o/christaylor-ai/.well-known/openid-configuration |
| JWKS | https://auth.christaylor.ai/application/o/christaylor-ai/jwks/ |

`https://christaylor.ai/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server` redirect to the authorization server rather than answering directly. That is deliberate: RFC 8414 section 3.3 requires the `issuer` in such a document to equal the URL it was fetched from, so serving the IdP's document under this hostname would publish metadata a conforming client must reject, and that a lenient one would misread as a claim that this origin issues tokens. It does not.

### Registration

Registration is **invite-gated**, and the reason is worth stating: it writes a service account into the Authentik that also fronts other products, so an open endpoint here would be an unauthenticated write into shared identity infrastructure. The cost of gating is close to nothing, because a credential buys ownership and attribution, never access. An agent without one loses the ability to own a game, and nothing else.

Ask for an invite (see below), then:

```json
{
  "agent_auth": {
    "skill": "https://christaylor.ai/auth.md",
    "capability_skills": [
      "https://christaylor.ai/.well-known/agent-skills/play-chess/SKILL.md"
    ],
    "register_uri": "https://christaylor.ai/agent/register",
    "identity_types_supported": [
      "anonymous"
    ],
    "anonymous": {
      "credential_types_supported": [
        "client_credentials"
      ],
      "claim_uri": "https://christaylor.ai/agent/register",
      "claim_requires": "a single-use invite code issued by Chris Taylor"
    },
    "claim_uri": "https://christaylor.ai/agent/register",
    "bearer_methods_supported": [
      "header"
    ],
    "authorization_servers": [
      "https://auth.christaylor.ai/application/o/christaylor-ai/"
    ],
    "methods": [
      {
        "type": "invite",
        "description": "Redeem a single-use invite code for a service account of your own.",
        "register_uri": "https://christaylor.ai/agent/register",
        "request": {
          "invite": "<invite code>",
          "name": "<optional label for your agent>"
        },
        "response": [
          "username",
          "password",
          "client_id",
          "token_endpoint"
        ],
        "credential_types_supported": [
          "client_credentials"
        ],
        "token_endpoint": "https://auth.christaylor.ai/application/o/token/",
        "grant_type": "client_credentials",
        "client_id": "christaylor-ai",
        "scope": "openid email profile",
        "notes": "No client secret is involved. The access token lasts one hour; repeat the exchange for a new one."
      }
    ],
    "anonymous_access": "Every endpoint on this origin works without a token."
  }
}
```

Present the result as `Authorization: Bearer <access_token>`, which is the only method supported. Nothing on this origin refuses an anonymous request.

## Etiquette

Cached responses are served with `Cache-Control`; please honour it. The search corpus is a single JSON document, so `portfolio_search` is cheap, and crawling every page to rebuild it is not. There is no rate limit today, which is a courtesy rather than a guarantee.

## Reaching a human

Call the `contact` tool on the MCP endpoint, or read https://christaylor.ai/llms.txt. The concierge quotes published pages; it does not speak for Chris.
