# `Anubis.Server.Frame`

The Anubis Frame — pure user state + read-only context.

## User fields

  * `assigns` - shared user data as a map. For HTTP transports, this inherits
    from `Plug.Conn.assigns`.

## Component maps

Runtime-registered components are stored in typed maps keyed by name/URI:

  * `tools` - `%{name => %Tool{}}`
  * `resources` - `%{uri => %Resource{}}`
  * `prompts` - `%{name => %Prompt{}}`
  * `resource_templates` - `%{name => %Resource{uri_template: ...}}`

## Pagination

  * `pagination_limit` - optional limit for listing operations

## Context

  * `context` - read-only `%Context{}`, refreshed by Session before each callback

# `server_component_t`

```elixir
@type server_component_t() ::
  Anubis.Server.Component.Tool.t()
  | Anubis.Server.Component.Resource.t()
  | Anubis.Server.Component.Prompt.t()
```

# `t`

```elixir
@type t() :: %Anubis.Server.Frame{
  assigns: map(),
  context: Anubis.Server.Context.t(),
  pagination_limit: non_neg_integer() | nil,
  prompts: %{optional(String.t()) =&gt; Anubis.Server.Component.Prompt.t()},
  resource_subscriptions: MapSet.t(String.t()),
  resource_templates: %{
    optional(String.t()) =&gt; Anubis.Server.Component.Resource.t()
  },
  resources: %{optional(String.t()) =&gt; Anubis.Server.Component.Resource.t()},
  task_id: String.t() | nil,
  tools: %{optional(String.t()) =&gt; Anubis.Server.Component.Tool.t()}
}
```

# `assign`

```elixir
@spec assign(t(), Enumerable.t()) :: t()
```

Assigns a value or multiple values to the frame.

## Examples

    frame = Frame.assign(frame, :status, :active)
    frame = Frame.assign(frame, %{status: :active, count: 5})
    frame = Frame.assign(frame, status: :active, count: 5)

# `assign`

```elixir
@spec assign(t(), key :: atom(), value :: any()) :: t()
```

# `assign_new`

```elixir
@spec assign_new(t(), key :: atom(), value_fun :: (-&gt; term())) :: t()
```

Assigns a value to the frame only if the key doesn't already exist.

The value is computed lazily using the provided function.

## Examples

    frame = Frame.assign_new(frame, :timestamp, fn -> DateTime.utc_now() end)

# `clear_components`

```elixir
@spec clear_components(t()) :: t()
```

Clears all runtime-registered components

# `from_saved`

```elixir
@spec from_saved(map()) :: t()
```

Reconstructs Frame from a previously saved map.

Restored: `assigns`, `pagination_limit`, `resource_subscriptions`. Runtime-only fields
(`tools`, `resources`, `prompts`, `resource_templates`) are initialized empty — their
validator functions are not serializable. `context` is left as the default struct and
will be set by Session before each callback invocation.

# `get_components`

```elixir
@spec get_components(t()) :: [server_component_t()]
```

Retrieves all runtime-registered components as a flat list

# `new`

```elixir
@spec new(assigns :: map()) :: t()
```

Creates a new frame with optional initial assigns.

## Examples

    iex> Frame.new()
    %Frame{assigns: %{}}

    iex> Frame.new(%{user: "alice"})
    %Frame{assigns: %{user: "alice"}}

# `put_pagination_limit`

```elixir
@spec put_pagination_limit(t(), non_neg_integer()) :: t()
```

Sets the pagination limit for listing operations.

## Examples

    frame = Frame.put_pagination_limit(frame, 10)
    frame.pagination_limit
    # => 10

# `register_prompt`

```elixir
@spec register_prompt(t(), String.t(), [prompt_opt]) :: t()
when prompt_opt:
       {:description, String.t() | nil}
       | {:arguments, map() | nil}
       | {:title, String.t() | nil}
```

Registers a prompt definition at runtime.

# `register_resource`

```elixir
@spec register_resource(t(), String.t(), [resource_opt]) :: t()
when resource_opt:
       {:title, String.t() | nil}
       | {:name, String.t() | nil}
       | {:description, String.t() | nil}
       | {:mime_type, String.t() | nil}
```

Registers a resource definition with a fixed URI.

For parameterized resources, use `register_resource_template/3` instead.

# `register_resource_template`

```elixir
@spec register_resource_template(t(), String.t(), [resource_template_opt]) :: t()
when resource_template_opt:
       {:title, String.t() | nil}
       | {:name, String.t()}
       | {:description, String.t() | nil}
       | {:mime_type, String.t() | nil}
```

Registers a resource template definition using a URI template (RFC 6570).

## Examples

    frame = Frame.register_resource_template(frame, "file:///{path}",
      name: "project_files",
      title: "Project Files",
      description: "Access files in the project directory"
    )

# `register_tool`

```elixir
@spec register_tool(t(), String.t(), [tool_opt]) :: t()
when tool_opt:
       {:description, String.t() | nil}
       | {:input_schema, map() | nil}
       | {:output_schema, map() | nil}
       | {:title, String.t() | nil}
       | {:annotations, map() | nil}
       | {:task_support, Anubis.Server.Component.Tool.task_support()}
```

Registers a tool definition at runtime.

# `resource_subscribed?`

```elixir
@spec resource_subscribed?(t(), uri :: String.t()) :: boolean()
```

Returns whether this session has an active subscription for the given URI.

# `subscribe_resource`

```elixir
@spec subscribe_resource(t(), uri :: String.t()) :: t()
```

Records that this session has subscribed to updates for the given resource
URI.

Idempotent — subscribing twice to the same URI is a no-op. Per the MCP spec,
the URI does not need to refer to a currently-registered resource.

# `to_saved`

```elixir
@spec to_saved(t()) :: map()
```

Serializes Frame for persistent storage.

Only `assigns` and `pagination_limit` are persisted. The following fields are
**runtime-only** and excluded from serialization:

  * `tools` — runtime-registered tool definitions (includes validator functions)
  * `resources` — runtime-registered resource definitions
  * `prompts` — runtime-registered prompt definitions
  * `resource_templates` — runtime-registered resource template definitions
  * `context` — rebuilt by Session before each callback invocation

Compile-time components (registered via the `component` macro) are always
available from the server module and do not need persistence.

# `unsubscribe_resource`

```elixir
@spec unsubscribe_resource(t(), uri :: String.t()) :: t()
```

Removes a previously-recorded subscription for the given URI.

---

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