# SSRS2 auth.md

How an AI agent authenticates to SSRS2's hosted MCP server. Its one credential is an SSRS2 licence key, given in one of
three ways: in the Authorization header; through an OAuth sign-in on ssrs2.net where a person pastes it once; or by
registering as an agent, anonymously, and having a person claim the registration with it (agentic registration, below).
SSRS2 has no user accounts: without a licence the server works too, with the trial's limits and watermark.

## Who this is for

Agents calling SSRS2's hosted MCP server (Streamable HTTP; its Server Card:
https://ssrs2.net/.well-known/mcp/server-card.json). Its tools, `validate_report`, `render_preview` and
`convert_report`, check RDLC and RDL reports and turn Markdown into PowerPoint, Word and PDF. It has two addresses:

- `https://ssrs2.net/mcp`: open. No credentials needed; a licence key or a sign-in token is taken when given.
- `https://mcp.ssrs2.net/a2a`: the same tools as an A2A agent (Agent2Agent 1.0, JSON-RPC; its card:
  https://ssrs2.net/.well-known/agent-card.json), its skills `convert`, `validate` and `preview`, open as `/mcp` is.
- `https://ssrs2.net/mcp/licensed`: the same tools for a client signed in with a licence. Without one it answers
  `401` with `WWW-Authenticate: Bearer resource_metadata="https://ssrs2.net/.well-known/oauth-protected-resource/mcp/licensed"`,
  which starts the client's sign-in.

## Without credentials

Call `https://ssrs2.net/mcp` as it is: no registration, no header. Calls without a licence are limited by the address
they come from, and PDF and image output carries SSRS2's trial watermark.

## With the licence key in a header

For clients that send headers (Claude Code, VS Code, Cursor, A2A clients), on any of the addresses:

```
Authorization: Bearer SSRS2-...
```

With a valid key the output has no watermark, and calls are counted by the licence rather than by the address, with a
higher limit. The key is checked by its signature on the server, without calling anywhere. On `/mcp`, a key that is not
valid makes the tool call fail with the reason (`badLicense`) rather than fall back to the trial: remove the header to
call without one.

## With an OAuth sign-in

For clients that sign in with OAuth and send no headers of their own (claude.ai, Claude Desktop, ChatGPT): add
`https://ssrs2.net/mcp/licensed` as the connector's address. The client follows MCP's authorization (OAuth 2.1):

1. The `401` names the Protected Resource Metadata (RFC 9728):
   https://ssrs2.net/.well-known/oauth-protected-resource/mcp/licensed. Its `resource` is
   `https://ssrs2.net/mcp/licensed`, its authorization server `https://ssrs2.net`. The root's,
   https://ssrs2.net/.well-known/oauth-protected-resource, describes `https://ssrs2.net`, SSRS2's licensed APIs on this
   origin, with the same authorization server: a token asked for it names both.
2. The authorization server's metadata (RFC 8414): https://ssrs2.net/.well-known/oauth-authorization-server.
3. The client registers by a Client ID Metadata Document (its `client_id` an https address ssrs2.net fetches), or, for
   clients without one, by Dynamic Client Registration (RFC 7591) at https://ssrs2.net/oauth/register. Public clients
   only: `token_endpoint_auth_method` `none`, with PKCE.
4. The person is sent to https://ssrs2.net/oauth/authorize (`response_type=code`, PKCE `S256`,
   `resource=https://ssrs2.net/mcp/licensed`, scope `license`). They paste the licence key; it is checked and not stored.
   The browser goes back to the client with a code and `iss` (RFC 9207).
5. The client trades the code at https://ssrs2.net/oauth/token for an access token (a JWT for
   `https://ssrs2.net/mcp/licensed`, an hour, signed by the keys at https://ssrs2.net/oauth/jwks.json) and a refresh
   token (30 days, a new one at each use; one used twice ends the connection). Neither outlives the licence.
6. The client sends the access token on every request: `Authorization: Bearer <access token>`. Calls run as with the
   key itself: no watermark, counted by the licence.

The token carries the licence key encrypted: it licenses only this server, not the package. To end a connection, revoke
its refresh token at https://ssrs2.net/oauth/revoke (RFC 7009).

## Registering as an agent

SSRS2 supports **agentic registration** (auth.md's protocol, github.com/workos/auth.md, v0.6.0): discover, register,
claim, exchange, call, revoke. Its one method is `anonymous`: SSRS2 has no accounts, so `service_auth` answers
`service_auth_not_enabled` and `identity_assertion` `issuer_not_enabled`.

1. **Discover.** The authorization server's metadata, https://ssrs2.net/.well-known/oauth-authorization-server, has an
   `agent_auth` block: `identity_endpoint` (also `register_uri`) https://ssrs2.net/agent/identity, `claim_endpoint`
   (also `claim_uri`) https://ssrs2.net/agent/identity/claim, `identity_types_supported` `["anonymous"]`. Its
   `grant_types_supported` lists `urn:ietf:params:oauth:grant-type:jwt-bearer` and `urn:workos:agent-auth:grant-type:claim`.
2. **Register.** `POST https://ssrs2.net/agent/identity` with `{"type": "anonymous"}`. The answer holds a
   `registration_id`, an `identity_assertion` (until the claim window closes, 24 hours), `pre_claim_scopes`
   `["trial"]`, a `claim_token` (given once: keep it in memory) and `post_claim_scopes` `["license"]`.
3. **Use the trial.** `POST https://ssrs2.net/oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`
   and `assertion=<identity_assertion>`: an access token of the `trial` scope. On `https://ssrs2.net/mcp` it is the same as
   no credentials (the trial's limits and watermark); on `/mcp/licensed` it is answered `403` `insufficient_scope`.
4. **Have a person claim it.** `POST https://ssrs2.net/agent/identity/claim` with `{"claim_token": "clm_..."}`. The
   answer's `claim_attempt` holds a six-digit `user_code`, a `verification_uri` on ssrs2.net, `expires_in` (600) and
   `interval` (5). Give the person both in one message: "Open this link, enter this code and your SSRS2 licence key."
   The licence key is the person's proof, so no email is needed.
5. **Poll.** `POST https://ssrs2.net/oauth/token` with `grant_type=urn:workos:agent-auth:grant-type:claim` and
   `claim_token=clm_...`, every `interval` seconds: `authorization_pending` while the person has not finished, `slow_down`
   when too fast, `expired_token` when the code's ten minutes pass (start a new attempt, step 4). Once claimed, the answer
   is an access token of the `license` scope (an hour) with a post-claim `identity_assertion` and `assertion_expires`
   (30 days, never past the licence). It is given once: the pre-claim assertion no longer counts.
6. **Renew.** When the access token expires, the JWT-bearer grant with the post-claim assertion gives a new one. When the
   assertion expires or the grant answers `invalid_grant`, register again.
7. **Revoke.** `POST https://ssrs2.net/oauth/revoke` with `token=<identity_assertion>` ends the registration.

## ssrs2.net's own requests

ssrs2.net sends one kind of automated request: when a client signs in (above), its sign-in server fetches the client's
metadata document, the https address the client gave as its `client_id` (claude.ai's, chatgpt.com's, vscode.dev's...),
once, then keeps it an hour. Nothing else is fetched or crawled. These requests carry the User-Agent
`ssrs2.net-oauth/1.0 (+https://ssrs2.net/auth.md)` and are signed with Web Bot Auth (RFC 9421 HTTP message signatures:
`Signature-Agent: "https://ssrs2.net"`, tag `web-bot-auth`, Ed25519); the key is published at
https://ssrs2.net/.well-known/http-message-signatures-directory.

## Getting a key

A key comes with an SSRS2 licence, one per company (terms: https://ssrs2.net/terms), and is emailed to the address
given at purchase. Nothing issues a key without a purchase.

- The person pays, by Stripe's checkout: https://ssrs2.net/buy/yearly or https://ssrs2.net/buy/three-years
- The agent pays, with a Stripe shared payment token: ACP (https://ssrs2.net/.well-known/acp.json) or MPP
  (https://ssrs2.net/openapi.json)

Step by step: https://ssrs2.net/.well-known/agent-skills/buy-ssrs2-licence/SKILL.md

## Handling the key

The key is a secret. Send it in the Authorization header or paste it on ssrs2.net's sign-in page only: never in a URL,
a tool argument, a report or a log. It is the company's key for SSRS2 everywhere: the same key licenses the
SSRS2.NETCore package and the local MCP server (environment variable `SSRS2_LICENSE`). The key itself cannot be revoked
or rotated; a sign-in's tokens and an agent's registration can.
