# Context

FastestMCP keeps runtime state explicit.

Every handler receives a `%FastestMCP.Context{}`. That is one of the core
design decisions in the library. Instead of rewriting function signatures or
hiding state in framework globals, FastestMCP makes the request state, session
state, auth state, and task state visible at the handler edge.

## Accessing Context

The normal shape is explicit arity-2 handlers:

```elixir
server =
  FastestMCP.server("context")
  |> FastestMCP.add_tool("process_file", fn %{"file_uri" => file_uri}, ctx ->
    :ok = FastestMCP.Context.info(ctx, "Processing #{file_uri}")

    %{
      file_uri: file_uri,
      request_id: ctx.request_id
    }
  end,
    input_schema: %{
      "type" => "object",
      "properties" => %{"file_uri" => %{"type" => "string"}},
      "required" => ["file_uri"]
    }
  end)
```

The same `%FastestMCP.Context{}` is passed to:

- tools
- resources
- resource templates
- prompts

That means the same runtime model applies no matter which component type is
executing.

## Convenience Helpers

The default style is explicit handler `ctx`.

### Explicit handler `ctx`

```elixir
FastestMCP.add_tool(server, "review", fn %{"subject" => subject}, ctx ->
  %{subject: subject, request_id: ctx.request_id}
end)
```

### `Context.current/0` and `current!/0`

Use these when nested helper code is only valid during an active request:

```elixir
defmodule MyApp.ReleaseHelpers do
  def current_request_summary do
    ctx = FastestMCP.Context.current!()

    %{
      request_id: ctx.request_id,
      session_id: ctx.session_id
    }
  end
end

FastestMCP.add_tool(server, "nested", fn _arguments, _ctx ->
  MyApp.ReleaseHelpers.current_request_summary()
end)
```

### `request_context`

```elixir
FastestMCP.add_tool(server, "request_info", fn _arguments, ctx ->
  request = FastestMCP.Context.request_context(ctx)

  %{
    request_id: request.request_id,
    transport: request.transport,
    path: request.path,
    headers: request.headers,
    meta: request.meta
  }
end)
```

### `client_id`

```elixir
FastestMCP.add_tool(server, "client_info", fn _arguments, ctx ->
  %{
    client_id: FastestMCP.Context.client_id(ctx),
    principal: ctx.principal
  }
end)
```

### Server access

```elixir
FastestMCP.add_tool(server, "server_info", fn _arguments, ctx ->
  server = FastestMCP.Context.server(ctx)

  %{
    server_name: server.name,
    schema_options: server.schema_options
  }
end)
```

Explicit handler `ctx` remains the primary style. `current/0` and `current!/0`
are convenience helpers for nested runtime code, not a new hidden-global
programming model.

## What Lives On Context

The context carries several different lifetimes of data:

- request state for one operation
- session state shared across requests with the same session id
- auth state such as principal, authentication status, verified scopes and
  audiences, and application capabilities
- task state when the operation is running as a background task
- lifespan context produced at server startup
- dependency resolvers declared on the server
- HTTP request metadata captured from the transport

That separation matters because each lifetime has different cleanup and failure
rules.

## Request Context Snapshots

`FastestMCP.Context.request_context/1` returns a stable
`%FastestMCP.RequestContext{}` wrapper with:

- `request_id`
- `transport`
- `path`
- `query_params`
- `headers`
- `meta`

That is the narrow convenience surface for code that wants request metadata
without depending on the full `%FastestMCP.Context{}` struct.

## Transport

The request snapshot also exposes the active transport:

```elixir
FastestMCP.add_tool(server, "connection_info", fn _arguments, ctx ->
  case FastestMCP.Context.request_context(ctx).transport do
    :stdio -> "Connected via stdio"
    :streamable_http -> "Connected via streamable HTTP"
    :in_process -> "Called in process"
    other -> "Connected via #{other || "unknown"}"
  end
end)
```

## Client Metadata

Clients can attach request-scoped metadata, and FastestMCP exposes it through
`request_context.meta`.

From the connected client:

```elixir
FastestMCP.Client.call_tool(client, "send_email", %{"to" => "ops@example.com"},
  meta: %{trace_id: "trace-123", user_id: "user-42"}
)
```

Inside the handler:

```elixir
FastestMCP.add_tool(server, "send_email", fn _arguments, ctx ->
  request = FastestMCP.Context.request_context(ctx)

  %{
    trace_id: request.meta["trace_id"],
    user_id: request.meta["user_id"]
  }
end)
```

This is useful for correlation ids, caller hints, or app-level request data
that should follow one MCP operation without being promoted to session state.

## Session State

Session state is the right place for conversation-local memory.

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("context")
  |> FastestMCP.add_tool("session_info", fn _arguments, ctx ->
    visits = Context.get_state(ctx, :visits, 0) + 1
    :ok = Context.set_state(ctx, :visits, visits)

    %{
      session_id: ctx.session_id,
      request_id: ctx.request_id,
      visits: visits
    }
  end)
```

Use session state when the value belongs to the client conversation, not to one
request and not to the whole server. HTTP, stdio, and normal in-process calls
use `ctx.state_scope == :session` by default.

Pass `state_scope: :request` when application values must reset for every
operation. In that mode, `set_state/4`, `get_state/3`, and `delete_state/2`
operate only on request-local storage; they never write a session backend
entry. HTTP still completes the normal initialize lifecycle and keeps a stable,
non-null `ctx.session_id`, negotiated version, client information,
capabilities, subscriptions, and task ownership.

FastestMCP exposes three related APIs:

- `Context.set_state/4`
- `Context.get_state/3`
- `Context.delete_state/2`

The older `put_session_state/3` and `get_session_state/3` helpers still work,
but `set_state` and `get_state` are now the preferred interface.

By default, session state is stored in a per-server in-memory backend. You can
swap that backend at server startup with `session_state_store: {module, opts}`.

One important detail:

```elixir
Context.set_state(ctx, :current_upload, socket, serializable: false)
```

`serializable: false` keeps the value request-scoped instead of writing it to
the session backend. Use that for values that should stay local to the current
call and should not be serialized or shared across requests.

## Application Sessions

Application sessions hold application-owned state independently of the MCP
transport session. They use the same configured `SessionStateStore`, but their
keys live in a separate hashed namespace.

Use the authenticated caller's private bucket when state should follow the same
principal across modern HTTP, stdio, legacy sessions, and background work:

```elixir
alias FastestMCP.ApplicationSession

FastestMCP.add_tool(server, "remember_preference", fn %{"theme" => theme}, ctx ->
  session = ApplicationSession.current!(ctx)
  :ok = ApplicationSession.put(session, :theme, theme)
  %{"stored" => true}
end)
```

Use an explicit session when the application needs an opaque handle:

```elixir
{:ok, session} = ApplicationSession.create(ctx)
session_id = ApplicationSession.id(session)

# In a later authenticated request:
{:ok, session} = ApplicationSession.fetch(ctx, session_id)
{:ok, theme} = ApplicationSession.get(session, :theme, "system")
```

Explicit sessions are scoped to the verified principal. Unknown, terminated,
and foreign identifiers return the same invalid-parameter error. Termination
deletes the entire explicit-session namespace.

Anonymous explicit sessions are disabled by default. Opt in only when the
random session identifier is intended to act as a bearer capability:

```elixir
FastestMCP.server("app", application_sessions: [allow_anonymous: true])
```

`ApplicationSession.current/1` always requires an authenticated principal,
even when anonymous explicit sessions are enabled. The application should use
a globally unambiguous verified principal, such as `{issuer, subject}`.

## Request State

Request state is scratch storage for the current operation only.

FastestMCP uses it internally for features such as dependency caching and
progress helpers, but it is also available to application code through:

- `Context.put_request_state/3`
- `Context.get_request_state/3`
- `Context.delete_request_state/2`

Use request state when a helper inside the current call stack needs to share
data without writing to the session.

## Dependencies

Dependencies are resolved from the context and cached once per request or
background task:

```elixir
server =
  FastestMCP.server("context")
  |> FastestMCP.add_dependency(:clock, fn -> DateTime.utc_now() end)
  |> FastestMCP.add_tool("time", fn _arguments, ctx ->
    %{now: Context.dependency(ctx, :clock)}
  end)
```

Use dependencies for application services or request-scoped resource handles.
The dedicated guide covers cleanup behavior and resolver shapes in more detail:

- [Dependency Injection](dependency-injection.md)

## Lifespan Context

Values created at startup are available through `ctx.lifespan_context`:

```elixir
server =
  FastestMCP.server("context")
  |> FastestMCP.add_lifespan(fn _server ->
    %{"config" => %{"region" => "eu-west-1"}}
  end)
  |> FastestMCP.add_tool("config", fn _arguments, ctx ->
    ctx.lifespan_context
  end)
```

Use lifespan context for runtime-wide state that should exist once per server
instance, not once per request.

## Auth State

Authenticators write normalized auth results back onto the context:

- `ctx.principal`
- `ctx.auth`
- `ctx.authenticated`
- `ctx.capabilities`
- `ctx.verified_scopes`
- `ctx.verified_audiences`
- `Context.client_id/1`

That gives tools, prompts, middleware, and providers one consistent view of
the authenticated caller.

`Context.client_id/1` currently derives from auth or principal data when it is
available. When auth does not provide one, it falls back to negotiated
`clientInfo.name` from the MCP initialize handshake for the current session.

## HTTP Context

The context also carries an immutable HTTP request snapshot when the operation
came from HTTP:

- `Context.http_request/1`
- `Context.http_headers/2`
- `Context.access_token/1`
- `Context.request_context/1`

Use these helpers when handler behavior legitimately depends on request
metadata. Keep that explicit; avoid pretending the transport does not exist
when it actually matters.

Incoming `Authorization` is deliberately absent from the public HTTP header
snapshots and from context inspection. Authentication still receives the raw
header at the transport boundary. `Context.access_token/1` remains the narrow,
explicit accessor for live-request code that needs the bearer token, while the
transport credential is not copied into background or detached task context.

## Background Task Context

When an operation runs as a background task, the context reflects that:

- `Context.background_task?/1`
- `Context.task_id/1`
- `Context.origin_request_id/1`
- `Context.task_store/1`

This is why the same handler code can still report progress, ask for
elicitation, or access task metadata after the original request has returned.

## Logging

Context-driven server logging uses `Context.log/4`:

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("context")
  |> FastestMCP.add_tool("run", fn _arguments, ctx ->
    :ok = Context.log(ctx, :info, "Starting work")
    :ok = Context.debug(ctx, "Debug detail")
    %{status: "ok"}
  end)
```

Supported levels are:

- `:debug`
- `:info`
- `:notice`
- `:warning`
- `:error`
- `:critical`
- `:alert`
- `:emergency`

FastestMCP also exposes convenience wrappers:

- `Context.debug/3`
- `Context.info/3`
- `Context.warning/3`
- `Context.error/3`

## Progress Reporting

Progress reporting is available through `Context.report_progress/4`:

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("context")
  |> FastestMCP.add_tool(
    "slow",
    fn _arguments, ctx ->
      Context.report_progress(ctx, 10, 100, "Starting")
      Context.report_progress(ctx, 60, 100, "Halfway")
      Context.report_progress(ctx, 100, 100, "Done")
      :done
    end,
    task: true
  )
```

This is most useful in background tasks or active HTTP requests that provide a
progress token.

## Sampling and Elicitation

Several higher-level features are just context operations:

- `Context.progress/1`
- `Context.report_progress/4`
- `Context.log/4`
- `Context.send_notification/3`
- `Context.sample/3`
- `Context.elicit/4`
- `Context.elicit_url/4`
- `Context.require_url_elicitation!/4`
- `Context.list_roots/2`
- `Context.cached_roots/1`
- `Context.list_peer_tasks/2`
- `Context.ping_peer/2`

Sampling lets the server ask the connected client model to generate content.
Form elicitation asks for schema-validated structured input, while URL
elicitation coordinates an identity-bound out-of-band interaction. On the
legacy profile these peer operations use the session coordinator over HTTP or
stdio. Modern handlers return `InputRequiredResult` and the client performs
the corresponding MRTR interaction without a session.

`Context.sample/3`, `Context.elicit/4`, and `Context.elicit_url/4` return an
immediate result by default. With `task: true`, sampling and elicitation return
a `%FastestMCP.PeerTask{}` only when the client negotiated the exact requester
task capability.

Protocol delivery helpers report failures explicitly. In particular,
`Context.log/4`, `Context.report_progress/4`, and
`Context.send_notification/3` can return delivery, lifecycle, rate, or state
errors. Custom notifications cannot use reserved standard MCP method names.

See:

- [Sampling and Interaction](sampling-and-interaction.md)
- [Background Tasks](background-tasks.md)

## Legacy Client Roots and Peer Ping

On `2025-11-25`, `Context.list_roots/2` requests the connected client's current filesystem
roots after verifying the negotiated `roots` capability. Successful results
are parsed into `%FastestMCP.Root{}` values and cached on the exact session:

```elixir
roots = Context.list_roots(ctx)

if Enum.any?(roots, &FastestMCP.Root.contains?(&1, "file:///workspace/app/mix.exs")) do
  %{inside_declared_root: true}
end
```

Pass `refresh: true` to bypass the cache. A negotiated
`notifications/roots/list_changed` refreshes the cache under runtime
supervision. Only canonical `file://` roots are accepted;
`FastestMCP.Root.safe_realpath/2` adds symlink-aware containment for paths on
the server's local filesystem.

`Context.ping_peer/2` sends an outbound MCP ping through the same session path
and returns `:ok` only for the standard empty-object result.

Core `2026-07-28` has no independent roots callback or ping method. Modern
tools, prompts, and resource reads request roots through an
`InputRequiredResult`; the connected client reuses its configured roots
handler during that MRTR round.

When the client negotiated `tasks.list`, `Context.list_peer_tasks/2` returns
`%{items: tasks, next_cursor: cursor}` for tasks owned by that peer. Continue
with the opaque `cursor:` only; a `page_size:` option is ignored and never sent
on the wire.

## Nested Resource and Prompt Access

FastestMCP exposes nested resource and prompt helpers directly on the context:

- `Context.list_resources/1`
- `Context.read_resource/2`
- `Context.list_prompts/1`
- `Context.render_prompt/3`

Those helpers preserve the current auth, request metadata, task context, and
legacy session when one component needs to call another surface inside the same
server.

Example:

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("nested-context")
  |> FastestMCP.add_resource("config://release", fn _arguments, _ctx ->
    %{name: "fastest_mcp", version: "0.1.0"}
  end)
  |> FastestMCP.add_tool("describe_release", fn _arguments, ctx ->
    config = Context.read_resource(ctx, "config://release")
    %{config: config}
  end)
```

## Resource Update Notifications

If a handler changes data that subscribed clients read through resources, it can
emit an update directly from the context:

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("resource-updates")
  |> FastestMCP.add_tool("refresh", fn _arguments, ctx ->
    :ok = Context.notify_resource_updated(ctx, "config://release")
    %{ok: true}
  end)
```

That produces `notifications/resources/updated` for subscribed initialized
HTTP or stdio sessions with a deliverable output sink.

## Session Visibility

Context also owns session-local visibility rules:

- `Context.enable_components/2`
- `Context.disable_components/2`
- `Context.reset_visibility/1`

These rules let one legacy session reveal or hide tools, resources, resource
templates, and prompts without mutating the global registry for every client.

Selectors support:

- `names`
- `keys`
- `tags`
- `components`
- `version`
- `match_all: true`

Visibility changes can produce session-specific:

- `notifications/tools/list_changed`
- `notifications/resources/list_changed`
- `notifications/prompts/list_changed`

when the visible set actually changes for that session.

## Legacy Direct Notifications

`Context.send_notification/3` lets a legacy handler send a raw MCP notification
over the active client session stream:

```elixir
alias FastestMCP.Context

server =
  FastestMCP.server("context-notifications")
  |> FastestMCP.add_tool("announce", fn _arguments, ctx ->
    {:ok, _delivery} =
      Context.send_notification(
        ctx,
        "com.example/notifications/build_completed",
        %{"buildId" => "build-42"}
      )

    %{ok: true}
  end)
```

That is mainly useful for advanced runtime integrations and custom
session-stream behavior. Standard MCP notification names are reserved; use the
typed resource, visibility, progress, logging, cancellation, task, roots, and
elicitation helpers for those methods.

## Current Compatibility Boundary

FastestMCP makes a few deliberate choices:

- explicit handler `ctx` is the primary style
- `Context.current/0` and `current!/0` are process-local convenience helpers,
  not the main programming model
- `ctx.server` is the server accessor; there is no separate `ctx.fastestmcp`
  field

## Choosing The Right Lifetime

Use:

- request state for temporary scratch data
- session state for conversation state
- dependencies for request-scoped services
- lifespan for server-wide startup state
- task metadata for long-running background execution

## Why This Shape

FastestMCP keeps context explicit because OTP lifetimes matter.

Request data, session data, auth state, startup state, and background task
state should not all feel like the same invisible dependency. The context makes
those boundaries visible, which keeps transport behavior, supervision, and
cleanup rules easier to understand.

## Related Guides

- [Tools](tools.md)
- [Resources](resources.md)
- [Prompts](prompts.md)
- [Dependency Injection](dependency-injection.md)
- [Lifespan](lifespan.md)
- [Background Tasks](background-tasks.md)
- [Sampling and Interaction](sampling-and-interaction.md)
- [Logging](logging.md)
