ConduitMcp.DSL (ConduitMCP v0.9.4)

Copy Markdown View Source

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
end

The 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.

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

annotations(opts)

(macro)

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

app(name, description, list)

(macro)

Defines an MCP App — a tool with a linked UI resource.

The app macro is a convenience that registers both:

  1. A tool with _meta.ui.resourceUri pointing to a ui:// resource
  2. 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
end

This 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
end

Note: The view path is read with File.read!/1 at runtime. For OTP releases where the working directory differs, use a manual tool + resource pair with Application.app_dir/2 instead.

arg(name, type, description \\ nil, opts \\ [])

(macro)

Defines a prompt argument.

Examples

arg :code, :string, "Code to review", required: true
arg :language, :string, "Programming language", default: "elixir"

compile_uri_template(template)

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.

complete(arg_name, fun)

(macro)

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

description(desc)

(macro)

Sets the description for a resource.

Example

resource "user://{id}" do
  description "User profile information"
  read MyUsers, :read
end

extract_uri_params_compiled(uri, param_names, regex)

Matches a URI against a pre-compiled template regex.

Uses the output of compile_uri_template/1 to avoid runtime regex compilation.

field(name, type, description \\ nil, opts \\ [])

(macro)

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

field(name, type, description, opts, list)

(macro)

get(fun)

(macro)

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

get(module, function)

(macro)

handle(fun)

(macro)

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

handle(module, function)

(macro)

icons(icon_list)

(macro)

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

items(type)

(macro)

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

items(type, list)

(macro)

meta(meta_map)

(macro)

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
end

The tool's tools/list entry will include:

%{
  "name" => "dashboard",
  "_meta" => %{"ui" => %{"resourceUri" => "ui://dashboard/app.html"}}
}

mime_type(type)

(macro)

Sets the MIME type for a resource.

Example

resource "file://{path}" do
  mime_type "text/plain"
  read MyFiles, :read
end

output_schema(schema)

(macro)

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

param(name, type, description \\ nil, opts \\ [])

(macro)

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 function fn(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

param(name, type, description, opts, list)

(macro)

Defines a parameter with nested fields (for objects) or items (for arrays).

precompile_template_regex(module, template)

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.

prompt(name, description, list)

(macro)

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

read(fun)

(macro)

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

read(module, function)

(macro)

resource(uri, list)

(macro)

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

scope(scope_string)

(macro)

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
end

Multiple 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

task_support(level)

(macro)

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 poll tasks/get and receive results via tasks/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

template_regex(module, template)

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).

title(value)

(macro)

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

tool(name, description, list)

(macro)

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

ui(resource_uri)

(macro)

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
end

See the MCP Apps guide for full details.

view(path)

(macro)

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