DSL for defining MCP servers with a clean, declarative syntax.
The DSL provides macros for defining tools, prompts, and resources without manually building JSON schemas and callback functions.
Example
defmodule MyApp.MCPServer do
use ConduitMcp.Server
tool "greet", "Greets a person" do
param :name, :string, "Name to greet", required: true
param :style, :string, "Greeting style", enum: ["formal", "casual"]
handle fn _conn, params ->
name = params["name"]
style = params["style"] || "casual"
greeting = if style == "formal", do: "Good day", else: "Hey"
text("#{greeting}, #{name}!")
end
end
tool "calculate", "Math operations" do
param :op, :string, "Operation", enum: ~w(add sub mul div), required: true
param :a, :number, "First number", required: true
param :b, :number, "Second number", required: true
handle MyMath, :calculate # MFA
end
prompt "code_review", "Code review assistant" do
arg :code, :string, "Code to review", required: true
arg :language, :string, "Language", default: "elixir"
get fn _conn, args ->
[
system("You are a code reviewer"),
user("Review this #{args["language"]} code:\n#{args["code"]}")
]
end
end
resource "user://{id}" do
description "User profile"
mime_type "application/json"
read fn _conn, params, _opts ->
user = MyApp.Users.get!(params["id"])
json(user)
end
end
endThe DSL automatically generates:
- Tool/prompt/resource schemas
- Input validation (JSON Schema)
- handlelist* callbacks
- handle_call_tool/handle_get_prompt/handle_read_resource callbacks
Summary
Functions
Sets annotations for a tool.
Defines an MCP App — a tool with a linked UI resource.
Defines a prompt argument.
Pre-compiles a URI template into a regex and parameter name list.
Defines an autocomplete handler for a prompt argument.
Sets the description for a resource.
Matches a URI against a pre-compiled template regex.
Defines a nested field within an object parameter.
Defines the get handler for a prompt.
Defines the handler function for a tool.
Sets icon descriptors for a tool. Added in MCP spec 2025-11-25.
Defines the item type for an array parameter.
Attaches arbitrary _meta metadata to a tool definition.
Sets the MIME type for a resource.
Sets the JSON Schema for the tool's structured output.
Defines a parameter for a tool.
Defines a parameter with nested fields (for objects) or items (for arrays).
Stores a compiled URI template regex in :persistent_term so that it can
be looked up at request time without paying the per-process
Regex.recompile/1 cost incurred when a Regex struct is embedded via
Macro.escape/1.
Defines an MCP prompt.
Defines the read handler for a resource.
Defines an MCP resource.
Sets a required OAuth scope for a tool.
Declares the tool's support for asynchronous task execution.
Fetches a {param_names, regex} tuple previously stored by
precompile_template_regex/2, falling back to recompiling on cache miss
(defensive — should not normally happen).
Sets a human-readable display title for a tool.
Defines an MCP tool.
Shortcut for declaring a UI resource URI on a tool (MCP Apps).
Specifies the HTML file path for an MCP App.
Functions
Sets annotations for a tool.
Annotations provide hints about tool behavior to clients.
Options
:read_only- Tool doesn't modify state (default: not set):destructive- Tool may perform destructive operations (default: not set):idempotent- Calling multiple times has same effect (default: not set):open_world- Tool may interact with external systems (default: not set)
Example
tool "delete_user", "Deletes a user" do
annotations destructive: true, idempotent: true
param :id, :string, "User ID", required: true
handle fn _conn, %{"id" => id} -> ... end
end
Defines an MCP App — a tool with a linked UI resource.
The app macro is a convenience that registers both:
- A tool with
_meta.ui.resourceUripointing to aui://resource - A resource that serves the HTML file at that URI
Use view/1 inside the block to specify the HTML file path.
The ui:// URI is derived automatically from the tool name and filename.
Example
app "dashboard", "Health dashboard" do
param :format, :string, "Output format", default: "json"
view "priv/mcp_apps/dashboard.html"
handle fn _conn, params ->
json(%{cpu: 42, memory: 128})
end
endThis is equivalent to:
tool "dashboard", "Health dashboard" do
ui "ui://dashboard/dashboard.html"
param :format, :string, "Output format", default: "json"
handle fn _conn, params -> json(%{cpu: 42, memory: 128}) end
end
resource "ui://dashboard/dashboard.html" do
description "UI for dashboard"
mime_type "text/html;profile=mcp-app"
read fn _conn, _params, _opts ->
app_html(File.read!("priv/mcp_apps/dashboard.html"))
end
endNote: The
viewpath is read withFile.read!/1at runtime. For OTP releases where the working directory differs, use a manualtool+resourcepair withApplication.app_dir/2instead.
Defines a prompt argument.
Examples
arg :code, :string, "Code to review", required: true
arg :language, :string, "Programming language", default: "elixir"
Pre-compiles a URI template into a regex and parameter name list.
Returns {param_names, compiled_regex} suitable for passing to
extract_uri_params_compiled/3. Call this at compile time and store
the result to avoid re-compiling the regex on every request.
Defines an autocomplete handler for a prompt argument.
Example
complete :language, fn _conn, prefix ->
~w(elixir python javascript rust)
|> Enum.filter(&String.starts_with?(&1, prefix))
end
Sets the description for a resource.
Example
resource "user://{id}" do
description "User profile information"
read MyUsers, :read
end
Matches a URI against a pre-compiled template regex.
Uses the output of compile_uri_template/1 to avoid runtime regex compilation.
Defines a nested field within an object parameter.
Examples
param :user, :object, "User data" do
field :name, :string, "Name", required: true
field :email, :string, "Email", required: true
field :address, :object, "Address" do
field :city, :string, "City"
field :zip, :string, "Zip code"
end
end
Defines the get handler for a prompt.
The handler should return a list of message objects.
Examples
get fn _conn, args ->
[
system("You are helpful"),
user("Question: #{args["question"]}")
]
end
get MyPrompts, :get_review
Defines the handler function for a tool.
Accepts either an anonymous function or an MFA tuple.
Examples
# Anonymous function
handle fn _conn, params ->
text("Result: #{params["input"]}")
end
# Module, function
handle MyModule, :my_function
# Function capture
handle &MyModule.my_function/2
Sets icon descriptors for a tool. Added in MCP spec 2025-11-25.
Accepts a list of icon maps, each with src (URL or data URI),
optional mimeType, width, height. Clients use these to render
the tool in their UI.
Example
tool "search", "Search the catalog" do
icons [%{"src" => "https://example.com/search.svg", "mimeType" => "image/svg+xml"}]
handle MyCatalog, :search
end
Defines the item type for an array parameter.
Examples
param :tags, :array, "Tags" do
items :string
end
param :users, :array, "Users" do
items :object do
field :name, :string, "Name"
field :email, :string, "Email"
end
end
Attaches arbitrary _meta metadata to a tool definition.
The _meta field is an open extension point in the MCP spec. Any map
can be passed — atom keys are automatically converted to string keys
in the JSON output.
Example
tool "dashboard", "Health dashboard" do
meta %{ui: %{resourceUri: "ui://dashboard/app.html"}}
handle fn _conn, _params -> json(%{ok: true}) end
endThe tool's tools/list entry will include:
%{
"name" => "dashboard",
"_meta" => %{"ui" => %{"resourceUri" => "ui://dashboard/app.html"}}
}
Sets the MIME type for a resource.
Example
resource "file://{path}" do
mime_type "text/plain"
read MyFiles, :read
end
Sets the JSON Schema for the tool's structured output.
When a tool returns a structured payload (via structured/2 helper
or by including "structuredContent" in the result map), this schema
describes its shape so clients can render/validate it. Added in MCP
spec 2025-11-25.
Example
tool "get_user", "Fetch a user" do
param :id, :string, "User id", required: true
output_schema %{
"type" => "object",
"properties" => %{
"id" => %{"type" => "string"},
"email" => %{"type" => "string"}
},
"required" => ["id", "email"]
}
handle MyUsers, :get
end
Defines a parameter for a tool.
Basic Options
:required- Mark parameter as required (default: false):enum- List of allowed values:default- Default value if not provided
Enhanced Validation Options (NimbleOptions)
:min- Minimum value for numbers (inclusive):max- Maximum value for numbers (inclusive):min_length- Minimum string length:max_length- Maximum string length:validator- Custom validation functionfn(value) -> boolean()
Examples
# Basic validation
param :name, :string, "User name", required: true
param :role, :string, "User role", enum: ["admin", "user", "guest"]
param :active, :boolean, "Active status", default: true
# Enhanced validation
param :age, :integer, "Age in years", min: 0, max: 150, required: true
param :username, :string, "Username", min_length: 3, max_length: 30
param :score, :number, "Score", min: 0.0, max: 100.0, default: 50.0
param :email, :string, "Email", validator: &ConduitMcp.Validation.Validators.email/1
# Complex validation with multiple constraints
param :priority, :string, "Task priority",
required: true,
enum: ["low", "medium", "high", "critical"],
validator: &MyApp.validate_priority/1
# Array with validation
param :tags, {:array, :string}, "Tags", max_length: 10
Defines a parameter with nested fields (for objects) or items (for arrays).
Stores a compiled URI template regex in :persistent_term so that it can
be looked up at request time without paying the per-process
Regex.recompile/1 cost incurred when a Regex struct is embedded via
Macro.escape/1.
Called at module load time from generated @on_load hooks.
Defines an MCP prompt.
Example
prompt "code_review", "Code review assistant" do
arg :code, :string, "Code to review", required: true
arg :language, :string, "Language", default: "elixir"
get fn _conn, args ->
[
system("You are a code reviewer"),
user("Review this code: #{args["code"]}")
]
end
complete :language, fn _conn, prefix ->
~w(elixir python javascript) |> Enum.filter(&String.starts_with?(&1, prefix))
end
end
Defines the read handler for a resource.
Handler signature: (conn, uri_params, opts) -> result
Examples
read fn _conn, %{"id" => id}, _opts ->
user = MyApp.Users.get!(id)
json(user)
end
read MyFiles, :read
Defines an MCP resource.
Examples
resource "user://{id}" do
description "User profile data"
mime_type "application/json"
read fn _conn, params, _opts ->
user = MyApp.Users.get!(params["id"])
json(user)
end
end
resource "file://{path}" do
mime_type "text/plain"
read MyFiles, :read
complete :path, &MyFiles.autocomplete/2
end
Sets a required OAuth scope for a tool.
When OAuth authentication is enabled, the handler will check that the token contains the required scope before executing the tool.
Example
tool "delete_user", "Deletes a user" do
scope "users:write"
param :id, :string, "User ID", required: true
handle fn _conn, %{"id" => id} -> ... end
endMultiple scopes can be required by calling scope multiple times or passing a space-separated string:
tool "admin_action", "Admin only" do
scope "admin users:write"
handle fn _conn, _params -> ... end
end
Declares the tool's support for asynchronous task execution.
Values:
:none(default, not emitted) — tool always returns synchronously:supported— tool MAY return a task id for long-running operations; clients can polltasks/getand receive results viatasks/result:required— tool ALWAYS returns a task id; the immediate response contains no result, only a task id to poll
Added in MCP spec 2025-11-25.
Example
tool "render_video", "Render a video from a script" do
task_support :supported
param :script, :string, "Script source", required: true
handle MyRenderer, :start
end
Fetches a {param_names, regex} tuple previously stored by
precompile_template_regex/2, falling back to recompiling on cache miss
(defensive — should not normally happen).
Sets a human-readable display title for a tool.
Distinct from name (the machine identifier used in tools/call),
title is shown to end users by MCP clients. Added in MCP spec
2025-11-25.
Example
tool "create_user", "Create a user account" do
title "Create User"
param :email, :string, "Email address", required: true
handle MyUsers, :create
end
Defines an MCP tool.
Examples
# Simple tool with inline handler
tool "greet", "Greets someone" do
param :name, :string, "Name", required: true
handle fn _conn, %{"name" => n} -> text("Hello #{n}!") end
end
# Tool with MFA handler
tool "calculate", "Calculator" do
param :a, :number, required: true
param :b, :number, required: true
handle MyMath, :add
end
# Tool with nested object
tool "create_user", "Creates user" do
param :user, :object, "User data", required: true do
field :name, :string, "Full name", required: true
field :email, :string, "Email", required: true
end
handle MyUsers, :create
end
Shortcut for declaring a UI resource URI on a tool (MCP Apps).
This is sugar for meta %{ui: %{resourceUri: uri}}. It links the tool
to an interactive HTML resource that MCP Apps-compatible hosts will
render as a sandboxed iframe.
Example
tool "dashboard", "Health dashboard" do
ui "ui://dashboard/app.html"
handle fn _conn, _params -> json(%{ok: true}) end
endSee the MCP Apps guide for full details.
Specifies the HTML file path for an MCP App.
Used inside an app block to set the HTML file that will be served
as the ui:// resource.
Example
app "dashboard", "Health dashboard" do
view "priv/mcp_apps/dashboard.html"
handle fn _conn, _params -> json(%{ok: true}) end
end