# `ExMCP.Authorization.CredentialStore`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/authorization/credential_store.ex#L59)

Pluggable persistence boundary for issuer-bound OAuth credentials.

ExMCP deliberately does not ship a file-backed secret store. Applications
can provide an OS keychain, encrypted database, or other appropriate adapter
as either `AdapterModule` or `{AdapterModule, adapter_state}`.

Registration records are stored under a versioned key containing the exact
authorization-server issuer and client ID. Adapters may maintain a separate
index from a non-secret local context (normally the MCP resource URL) to that
key, but `fetch_registration/3` validates both the returned key and record
before credentials can be reused.

Token keys include the exact issuer and client ID plus resource, audience,
subject/client identity, and granted scopes. Raw access and refresh tokens
are never part of a key.

Legacy records without an issuer are rejected with
`{:credential_migration_required, kind}`. Call `bind_legacy_registration/2`
or `bind_legacy_token/2` only after independently establishing the issuer;
ExMCP never attaches an unkeyed record to the currently discovered issuer.

## Adapter contract

An adapter implements this behaviour. `context` must be a stable,
non-secret local identifier. A registration fetch returns the exact storage
key with the record so ExMCP can reject a corrupt or cross-issuer index.

# `context`

```elixir
@type context() :: term()
```

# `key`

```elixir
@type key() :: registration_key() | token_key()
```

# `registration_key`

```elixir
@type registration_key() ::
  {:ex_mcp_oauth_credential, 1, :registration, String.t(), String.t()}
```

# `store`

```elixir
@type store() :: module() | {module(), term()}
```

# `token_key`

```elixir
@type token_key() ::
  {:ex_mcp_oauth_credential, 1, :token, String.t(), String.t(), term(), term(),
   term(), [String.t()]}
```

# `fetch_registration`

```elixir
@callback fetch_registration(context(), issuer :: String.t(), adapter_state :: term()) ::
  {:ok, key(), ExMCP.Authorization.CredentialStore.Registration.t() | map()}
  | :not_found
  | {:error, term()}
```

# `fetch_token`

```elixir
@callback fetch_token(token_key(), adapter_state :: term()) ::
  {:ok, ExMCP.Authorization.CredentialStore.Token.t() | map()}
  | :not_found
  | {:error, term()}
```

# `put_registration`

```elixir
@callback put_registration(
  context(),
  registration_key(),
  ExMCP.Authorization.CredentialStore.Registration.t(),
  adapter_state :: term()
) :: :ok | {:error, term()}
```

# `put_token`

```elixir
@callback put_token(
  token_key(),
  ExMCP.Authorization.CredentialStore.Token.t(),
  adapter_state :: term()
) ::
  :ok | {:error, term()}
```

# `bind_legacy_registration`

```elixir
@spec bind_legacy_registration(map(), String.t()) ::
  {:ok, ExMCP.Authorization.CredentialStore.Registration.t()} | {:error, term()}
```

Explicitly binds a legacy registration after its issuer is independently verified.

# `bind_legacy_token`

```elixir
@spec bind_legacy_token(map(), map()) ::
  {:ok, ExMCP.Authorization.CredentialStore.Token.t()} | {:error, term()}
```

Explicitly binds a legacy token after every partition field is independently verified.

# `fetch_registration`

```elixir
@spec fetch_registration(store(), context(), String.t()) ::
  {:ok, ExMCP.Authorization.CredentialStore.Registration.t()}
  | :not_found
  | {:error, term()}
```

Fetches and validates a registration for an exact issuer.

# `fetch_token`

```elixir
@spec fetch_token(store(), ExMCP.Authorization.CredentialStore.Token.t() | map()) ::
  {:ok, ExMCP.Authorization.CredentialStore.Token.t()}
  | :not_found
  | {:error, term()}
```

Fetches a token only from its complete authorization partition.

# `put_registration`

```elixir
@spec put_registration(
  store(),
  context(),
  ExMCP.Authorization.CredentialStore.Registration.t() | map()
) ::
  :ok | {:error, term()}
```

Persists a validated issuer-bound registration.

# `put_token`

```elixir
@spec put_token(store(), ExMCP.Authorization.CredentialStore.Token.t() | map()) ::
  :ok | {:error, term()}
```

Persists a token under its complete, non-secret authorization key.

# `registration_key`

```elixir
@spec registration_key(String.t(), String.t()) ::
  {:ok, registration_key()} | {:error, term()}
```

Builds the versioned persistence key for a client registration.

# `token_key`

```elixir
@spec token_key(ExMCP.Authorization.CredentialStore.Token.t() | map()) ::
  {:ok, token_key()} | {:error, term()}
```

Builds a complete token partition key without including token material.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
