# `Urchin.Session`
[🔗](https://github.com/urth-inc/urchin/blob/v0.4.0/lib/urchin/session.ex#L1)

Per-session state for the Streamable HTTP transport.

A session is created when a client completes the `initialize` handshake and is
addressed thereafter by its `MCP-Session-Id`. It tracks:

  * negotiated protocol version, client info and capabilities
  * the requested minimum log level and resource subscriptions
  * the connected GET ("general") SSE stream plus a replay buffer for resumption
  * in-flight POST requests, so `notifications/cancelled` can stop them
  * outbound server-to-client requests, so client responses can be correlated

Transport (`Plug`) processes and handler tasks talk to this process; it never owns
an HTTP connection itself.

# `cancel_outbound`

```elixir
@spec cancel_outbound(pid(), String.t()) :: :ok
```

Drops a pending outbound request (e.g. on timeout).

# `cancelled?`

```elixir
@spec cancelled?(pid(), Urchin.JSONRPC.id()) :: boolean()
```

Returns true if the given in-flight request has been cancelled.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `finish_request`

```elixir
@spec finish_request(pid(), Urchin.JSONRPC.id()) :: :ok
```

Removes a completed in-flight POST request.

# `generate_id`

```elixir
@spec generate_id() :: String.t()
```

Generates a cryptographically random, visible-ASCII session id.

# `handle_client_message`

```elixir
@spec handle_client_message(pid(), Urchin.JSONRPC.decoded()) :: :ok
```

Handles a client-originated notification or response delivered over POST.

Notifications (`notifications/cancelled`, ...) update session state; responses are correlated
to a pending outbound request. `notifications/initialized` is committed synchronously via
`mark_initialized/1`, not through this path.

# `mark_initialized`

```elixir
@spec mark_initialized(pid()) :: :ok
```

Marks the session initialized synchronously (after notifications/initialized).

# `notify`

```elixir
@spec notify(pid(), String.t(), map() | nil) :: :ok
```

Sends an unsolicited notification to the client on the general stream.

# `register_general_stream`

```elixir
@spec register_general_stream(pid(), pid(), {String.t(), non_neg_integer()} | nil) ::
  {:ok, String.t(), [{String.t(), iodata()}]}
```

Registers (or replaces) the GET general stream, returning events to replay.

# `register_outbound`

```elixir
@spec register_outbound(pid(), pid()) :: String.t()
```

Allocates an outbound request id and records the caller awaiting the response.

# `set_log_level`

```elixir
@spec set_log_level(pid(), String.t()) :: :ok
```

Sets the minimum log level the client wishes to receive.

# `snapshot`

```elixir
@spec snapshot(pid()) :: map()
```

Returns a snapshot of the data needed to build a request context.

# `start`

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

Starts a session under the session supervisor and returns its id and pid.

# `start_request`

```elixir
@spec start_request(pid(), Urchin.JSONRPC.id(), pid(), pid()) :: String.t()
```

Registers an in-flight POST request so it can be cancelled, returning a unique
per-session SSE stream id for that request's response stream.

# `subscribe`

```elixir
@spec subscribe(pid(), String.t()) :: :ok
```

Records a resource subscription.

# `subscriptions`

```elixir
@spec subscriptions(pid()) :: MapSet.t()
```

Returns the set of subscribed resource URIs.

# `terminate`

```elixir
@spec terminate(pid()) :: :ok
```

Terminates a session process.

# `unsubscribe`

```elixir
@spec unsubscribe(pid(), String.t()) :: :ok
```

Removes a resource subscription.

# `whereis`

```elixir
@spec whereis(String.t()) :: pid() | nil
```

Looks up a live session pid by id.

---

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