Resources, Templates & Prompts

Copy Markdown View Source

Static resources

A resource is a module with a fixed URI and a read/2:

defmodule MyApp.Resources.Config do
  use Noizu.MCP.Server.Resource,
    uri: "config://app",
    name: "App Config",
    description: "Application configuration",
    mime_type: "application/json",
    subscribable: true

  @impl true
  def read("config://app", _ctx), do: {:ok, Jason.encode!(MyApp.Config.current())}
end

Register it with resource MyApp.Resources.Config on the server. read/2 returns:

  • {:ok, binary} — text contents with the declared MIME type
  • {:ok, {:blob, binary}} — binary contents (base64-encoded on the wire)
  • {:ok, %Noizu.MCP.Types.ResourceContents{}} or a list of them — full control
  • {:error, Noizu.MCP.Error.resource_not_found(uri)} — the spec's -32002

Subscriptions

Declare subscribable: true and clients can resources/subscribe. When the underlying data changes, notify subscribers from anywhere in your app — the server module exports a fan-out helper:

MyApp.MCP.notify_resource_updated("config://app")

Every session subscribed to that URI receives notifications/resources/updated. Similarly, when the set of tools/resources/prompts changes:

MyApp.MCP.notify_changed(:tools)      # or :resources, :prompts

Resource templates (RFC 6570)

Templates expose URI families. Matched variables arrive as an atom-keyed map:

defmodule MyApp.Resources.TableSchema do
  use Noizu.MCP.Server.ResourceTemplate,
    uri_template: "db://{table}/schema",
    name: "Table Schema",
    mime_type: "application/json"

  @impl true
  def read(_uri, %{table: table}, _ctx) do
    case MyApp.Repo.table_schema(table) do
      {:ok, schema} -> {:ok, Jason.encode!(schema)}
      :error -> {:error, Noizu.MCP.Error.resource_not_found("db://#{table}/schema")}
    end
  end

  # Optional: argument completion for editors/clients
  @impl true
  def complete(:table, prefix, _ctx),
    do: {:ok, Enum.filter(MyApp.Repo.table_names(), &String.starts_with?(&1, prefix))}

  # Optional: enumerate concrete instances in resources/list
  @impl true
  def list(_ctx) do
    {:ok,
     for table <- MyApp.Repo.table_names() do
       %Noizu.MCP.Types.Resource{uri: "db://#{table}/schema", name: "#{table} schema"}
     end}
  end
end

Register with resource_template MyApp.Resources.TableSchema.

Prompts

defmodule MyApp.Prompts.CodeReview do
  use Noizu.MCP.Server.Prompt,
    name: "code_review",
    description: "Review code for quality issues"

  arguments do
    arg :code, required: true, description: "The code to review"
    arg :style, description: "Review style", complete: ["strict", "friendly"]
  end

  @impl true
  def get(%{"code" => code} = args, _ctx) do
    style = args["style"] || "strict"

    {:ok,
     [
       Noizu.MCP.Types.PromptMessage.user("Review this code (style: #{style}):"),
       Noizu.MCP.Types.PromptMessage.user(code)
     ]}
  end
end
  • Prompt arguments arrive string-keyed (they are free-form spec-side).
  • PromptMessage.user/1 and assistant/1 accept a string or Noizu.MCP.Types.Content structs.
  • Return {:ok, messages} or {:ok, messages, description: "..."} to override the description per call.

Hidden resources, templates & prompts

hidden: true works on resources, resource templates, and prompts exactly as it does on tools — the item is omitted from resources/list / resources/templates/list / prompts/list but stays fully readable/gettable by URI or name:

use Noizu.MCP.Server.Resource, uri: "internal://secrets", hidden: true
use Noizu.MCP.Server.Prompt, name: "internal_prompt", hidden: true

# or per registration:
resource MyApp.Resources.Config, hidden: true
prompt MyApp.Prompts.CodeReview, hidden: true

Hand-written list callbacks can opt hidden items back in: the registry helpers behind the generated defaults (Noizu.MCP.Server.Features.Resources.list_registered/5, list_registered_templates/3, and Noizu.MCP.Server.Features.Prompts.list_registered/3) take include_hidden: true. The full hidden-items story — overrides, the catalog discovery tool, session-gated visibility — lives in Toolkits, Categories & Hidden Tools.

Completion

completion/complete requests are routed automatically:

  • complete: ["a", "b"] on an arg — static prefix-filtered suggestions
  • def complete(arg_name, prefix, ctx) on a prompt or resource-template module — dynamic; return {:ok, values} or {:ok, values, has_more: bool, total: n}

Responses are capped at 100 values per the spec.

Pagination

All list endpoints paginate automatically with opaque cursors when a DSL-registered collection is large, and hand-written handle_list_* callbacks receive cursor and return {:ok, items, next_cursor | nil} — see Tools & Schemas for the behaviour-only pattern.