Sake.Server behaviour (Sake v0.4.3)

Copy Markdown View Source

The server module lists the components and their scopes.

defmodule MyApp.MCP do
  use Sake.Server, name: "myapp", version: "1.0.0", instructions: "..."
  scope "issues:read", "Read issues"
  tools [MyApp.MCP.SearchIssues]
  resources [MyApp.MCP.Issue]
  prompts [MyApp.MCP.Triage]
end

Options: name, version (required), instructions, ttl (default 300 000 ms), cache_scope (:public or :private; default: private when the endpoint has authorization, public otherwise), page_size (default 100), extensions (a map for extension negotiation), authentication (how the person must have signed in to call a tool, read a resource or get a prompt of this server, see below).

On the modern protocol, list and read results carry ttlMs: the server's ttl, or less. A tool, resource or prompt with its own ttl: shortens the ttlMs of every list page that includes it, and a resource's ttl: also sets the ttlMs of its resources/read result (the shortest ttl always wins).

middleware event, plugs runs host code around the server's calls (see Sake.Middleware); declare it once or more per event, and the plugs run in the order declared:

middleware :before_call, [MyApp.MCP.Audit, {MyApp.MCP.Tenant, field: :org_id}]

mount server, opts makes another server's tools, resources and prompts this server's, so that each team or context can own its part of a large server:

mount MyApp.MCP.Billing, prefix: "billing", scope: "billing"

With prefix:, tool and prompt names take the prefix and an underscore (billing_create_invoice) and resource URIs take it as the first segment after the scheme (invoice://billing/{id}, ui://billing/board); scope: (one or many) adds required scopes to every component mounted. only: or except: (a list of the mounted server's components, each a module, a tool or prompt name, or a resource URI, as that server declares it) mounts some of them:

mount MyApp.MCP.Billing, prefix: "billing", except: [MyApp.MCP.Billing.Refund]

See guides/composing.md for what the mounted server's own middleware, instructions, scopes and settings become. A Sake.Proxy mounts as any server does, with the components of its upstream.

Step-up authentication

A tool, resource or prompt with authentication: [acr: ["mfa"], max_age: 300] needs a token whose person signed in with one of those authentication context classes (acr), at most that many seconds ago (max_age), either optional (RFC 9470). The server's own authentication: applies to all its components, its mounted servers' too: an acr must then be among both lists, and the shorter max_age wins. A token that does not meet it is refused with a 401 whose insufficient_user_authentication challenge carries acr_values and max_age, for the client to have the person sign in again; a token that lacks the component's scopes as well gets that challenge, naming the scopes too, so that one new grant covers both. The components stay in the lists, and a request without a token (an endpoint without auth:, stdio) is not gated, as for scopes. A requirement that depends on the arguments is a middleware's: see Sake.Middleware.require_authentication/2. max_age: 0 means a sign-in within the last minute. It gates a tool call, a resource read and a prompt get; lists, completions, subscriptions and the requests that follow a task (which was checked when it started) are not.

Summary

Functions

Whether the request may see or call the component: no token means no gating.

Mounts another server's tools, resources and prompts in this one. Options: prefix:, scope:, and only: or except: (see the module documentation).

Tells listening clients that the list of tools, resources or prompts changed.

The prompt named name; see tool/3.

The server's prompts as the request sees them; see tools/2.

The resource uri names, and the template's parameters: one of the server's own, or, with the request's context, one of its proxies'. A proxy serves what its upstream lists, and any other URI under its mount's prefix (a resource link in a proxied result, say), which its upstream answers for.

Tells subscribed clients that a resource changed.

The server's resources as the request sees them; see tools/2.

The tool named name: one of the server's own (tool/2), or, with the request's context, one its proxies list for the request (tools/2).

The server's tools as the request sees them: its own and its mounted servers', then those its proxies (Sake.Proxy) list for the request's principal. A proxied tool whose name is already taken is left out: an upstream never shadows a local component.

Functions

allowed?(arg1, context)

@spec allowed?(struct(), Sake.Context.t()) :: boolean()

Whether the request may see or call the component: no token means no gating.

capabilities(server)

@spec capabilities(module()) :: map()

middleware(event, plugs)

(macro)

mount(server, opts \\ [])

(macro)

Mounts another server's tools, resources and prompts in this one. Options: prefix:, scope:, and only: or except: (see the module documentation).

notify_list_changed(server, kind)

@spec notify_list_changed(module(), :tools | :resources | :prompts) :: :ok

Tells listening clients that the list of tools, resources or prompts changed.

prompt(server, name, ctx \\ nil)

@spec prompt(module(), String.t(), Sake.Context.t() | nil) ::
  {:ok, Sake.Prompt.Spec.t()} | :error

The prompt named name; see tool/3.

prompts(list)

(macro)

prompts(server, ctx)

@spec prompts(module(), Sake.Context.t()) :: [Sake.Prompt.Spec.t()]

The server's prompts as the request sees them; see tools/2.

resource(server, uri, ctx \\ nil)

@spec resource(module(), String.t(), Sake.Context.t() | nil) ::
  {:ok, Sake.Resource.Spec.t(), map()} | :error

The resource uri names, and the template's parameters: one of the server's own, or, with the request's context, one of its proxies'. A proxy serves what its upstream lists, and any other URI under its mount's prefix (a resource link in a proxied result, say), which its upstream answers for.

resource_updated(server, uri)

@spec resource_updated(module(), String.t()) :: :ok

Tells subscribed clients that a resource changed.

resources(list)

(macro)

resources(server, ctx)

@spec resources(module(), Sake.Context.t()) :: [Sake.Resource.Spec.t()]

The server's resources as the request sees them; see tools/2.

scope(name, description)

(macro)

tool(server, name, ctx \\ nil)

@spec tool(module(), String.t(), Sake.Context.t() | nil) ::
  {:ok, Sake.Tool.Spec.t()} | :error

The tool named name: one of the server's own (tool/2), or, with the request's context, one its proxies list for the request (tools/2).

tools(list)

(macro)

tools(server, ctx)

@spec tools(module(), Sake.Context.t()) :: [Sake.Tool.Spec.t()]

The server's tools as the request sees them: its own and its mounted servers', then those its proxies (Sake.Proxy) list for the request's principal. A proxied tool whose name is already taken is left out: an upstream never shadows a local component.