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())}
endRegister 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, :promptsResource 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
endRegister 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/1andassistant/1accept a string orNoizu.MCP.Types.Contentstructs.- 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: trueHand-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 anarg— static prefix-filtered suggestionsdef 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.