# Shared Forest: agent authentication (auth.md)

Shared Forest has one protected resource: the product MCP server at `https://sharedforest.com/mcp`.
Most tools need no credentials. Tools that act for a person need an OAuth 2.1 access token from a Shared Forest account.

| Needs no token | Needs a token |
|---|---|
| get_forest_stats, get_forest, list_trees, plant_tree, explain_shared_forest, get_price_quote, design_tree, get_design, find_spots, the docs MCP server `https://sharedforest.com/mcp/docs`, the REST read endpoints | start_sponsorship (`sponsor:write`), get_order (`account:read` or `sponsor:write`), my_orders and my_trees (`account:read`) |

## Discover

1. Call a protected tool without a token. The server answers HTTP 401 with `WWW-Authenticate: Bearer resource_metadata="https://sharedforest.com/.well-known/oauth-protected-resource/mcp", scope="sponsor:write"`.
2. Read the protected resource metadata (RFC 9728): [https://sharedforest.com/.well-known/oauth-protected-resource/mcp](https://sharedforest.com/.well-known/oauth-protected-resource/mcp). It names the resource, the authorization server and `scopes_supported`.
3. Read the authorization server metadata (RFC 8414): [https://sharedforest.com/.well-known/oauth-authorization-server](https://sharedforest.com/.well-known/oauth-authorization-server). It lists the endpoints below.

## Pick a method

Use the OAuth 2.1 authorization code flow with PKCE (`code_challenge_method=S256` only; `plain` is rejected, and PKCE is required for every client).
There is no agent_auth identity_endpoint, no identity_assertion (ID-JAG) and no service_auth: a human with a Shared Forest account always approves the connection.
Anonymous use needs no method: omit the Authorization header.

| Scope | Grants |
|---|---|
| `forest:read` | Read the forest, prices and designs. Always granted; the same data is public. |
| `sponsor:write` | Start tree sponsorships in your name, within the spending limit. You still confirm and pay each order yourself. |
| `account:read` | Read your orders and your sponsored trees. |

## Register

Register your client once with Dynamic Client Registration (RFC 7591):

```sh
curl -s -X POST https://sharedforest.com/oauth/register -H 'Content-Type: application/json' \
  -d '{"client_name":"My agent","redirect_uris":["http://127.0.0.1:33418/callback"],"token_endpoint_auth_method":"none"}'
```

Redirect URIs must be https, http on a loopback host, or a private-use scheme of a native app. The authorize request must use one of them exactly.

## Claim

Send the user's browser to the authorization endpoint:

```text
https://sharedforest.com/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=<exact redirect_uri>&scope=sponsor:write%20account:read&state=<random>&code_challenge=<BASE64URL(SHA256(verifier))>&code_challenge_method=S256&resource=https%3A%2F%2Fsharedforest.com%2Fmcp
```

The user logs in (a verified email is required), sees your client name, the redirect host and the scopes, and sets a spending limit for `sponsor:write`: max EUR per order (1-900) and max EUR per UTC day (1-2000). The user can untick scopes. The redirect carries `code`, `state` and `iss`.

## Exchange

```sh
curl -s -X POST https://sharedforest.com/oauth/token -d grant_type=authorization_code -d code=<code> \
  -d client_id=<client_id> -d redirect_uri=<redirect_uri> -d code_verifier=<verifier>
```

The response holds `access_token` (valid 15 minutes), `refresh_token`, `expires_in` and the granted `scope`. A code works once. Refresh with `grant_type=refresh_token`: every refresh returns a new refresh token. The previous refresh token stops working once the new one is used. A grant without use ends after 30 days.

## Use the access_token

Send `Authorization: Bearer <access_token>` on every POST to `https://sharedforest.com/mcp`. The token is bound to that resource.
start_sponsorship creates an unpaid order in the user's account and returns `confirm_url`. The user confirms and pays on that page; an agent never pays for a consumer (German consumer law). An order above the spending limit fails before anything is created.

## Errors

- 401 with `WWW-Authenticate: Bearer resource_metadata="...", error="invalid_token"`: the token is unknown, expired or revoked. Refresh it or connect again. Omit the header to use the public tools.
- 403 with `error="insufficient_scope", scope="..."`: the user did not grant that scope. Ask the user to connect again.
- Tool result with `isError: true` and "spending limit": the order is above the limit. Ask the user for a smaller order or a new connection with a higher limit.
- Token endpoint errors use RFC 6749 JSON (`invalid_grant`, `invalid_request`, `invalid_client`).

## Revocation

Revoke a token (RFC 7009) at the token endpoint. Revoking the refresh token ends the whole grant:

```sh
curl -s -X POST https://sharedforest.com/oauth/token -d token=<refresh_token> -d token_type_hint=refresh_token -d client_id=<client_id>
```

A new consent for the same client replaces the old grant. Deleting the Shared Forest account revokes every grant.
