# `ConduitMcp.DSL.Helpers`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.10.1/lib/conduit_mcp/dsl/helpers.ex#L1)

Helper macros for building MCP responses in the DSL.

These helpers provide a convenient way to construct properly formatted
MCP responses without manually building the response maps.

## Response Helpers

- `text/1` - Returns a text content response
- `json/1` - Returns JSON-encoded text content
- `raw/1` - Returns raw data directly (bypasses MCP content wrapping)
- `error/1` or `error/2` - Returns an error response
- `image/1` - Returns an image content response

## Prompt Message Helpers

- `system/1` - Creates a system role message
- `user/1` - Creates a user role message
- `assistant/1` - Creates an assistant role message

## Examples

    # Text response
    text("Hello, world!")
    # => {:ok, %{"content" => [%{"type" => "text", "text" => "Hello, world!"}]}}

    # JSON response
    json(%{status: "ok", count: 42})
    # => {:ok, %{"content" => [%{"type" => "text", "text" => "{\"status\":\"ok\",\"count\":42}"}]}}

    # Raw response (bypasses MCP wrapping)
    raw(%{status: "ok", count: 42})
    # => {:ok, %{"status" => "ok", "count" => 42}}

    # Error response
    error("Not found")
    # => {:error, %{"code" => -32000, "message" => "Not found"}}

    # Custom error code
    error("Invalid params", -32602)
    # => {:error, %{"code" => -32602, "message" => "Invalid params"}}

    # Prompt messages
    [
      system("You are a helpful assistant"),
      user("What is 2+2?")
    ]

# `app_html`
*macro* 

Creates a resource content response for MCP Apps UI.

Shortcut for `raw_resource(content, "text/html;profile=mcp-app")`.
The `text/html;profile=mcp-app` MIME type is required by MCP Apps hosts
to render the HTML as a sandboxed iframe.

## Example

    resource "ui://dashboard/app.html" do
      mime_type "text/html;profile=mcp-app"

      read fn _conn, _params, _opts ->
        html = File.read!("priv/mcp_apps/dashboard.html")
        app_html(html)
      end
    end

# `assistant`
*macro* 

Creates an assistant role message for prompts.

## Example

    def handle_get_prompt(_conn, "example", _args) do
      {:ok, %{
        "messages" => [
          user("Show me an example"),
          assistant("Here's an example: ...")
        ]
      }}
    end

# `audio`
*macro* 

Creates an audio content response.

## Example

    handle fn _conn, %{"file" => file} ->
      data = File.read!(file) |> Base.encode64()
      audio(data, "audio/wav")
    end

# `error`
*macro* 

Creates an error response.

## Examples

    error("User not found")
    # => {:error, %{"code" => -32000, "message" => "User not found"}}

    error("Invalid parameters", -32602)
    # => {:error, %{"code" => -32602, "message" => "Invalid parameters"}}

# `execution_error`
*macro* 

Reports a tool *execution* error to the client.

Per MCP spec, tool execution errors (the operation ran but failed in a way
the LLM can interpret and possibly recover from) are distinct from protocol
errors. They are returned as a successful result with `"isError" => true`,
not as a JSON-RPC error. Use `error/2` for protocol errors (bad params,
internal failures, etc.) and `execution_error/1` for "the call ran but
this is what went wrong" so the LLM can self-correct.

## Example

    tool "fetch_user", "Fetch a user by id" do
      param :id, :string, "User id", required: true

      handle fn _conn, %{"id" => id} ->
        case MyUsers.fetch(id) do
          {:ok, user} -> json(user)
          {:error, :not_found} -> execution_error("User #{id} not found")
        end
      end
    end

# `image`
*macro* 

Creates an image content response.

## Example

    def handle_call_tool(_conn, "generate_chart", params) do
      image_url = MyCharts.generate(params)
      image(image_url)
    end

# `json`
*macro* 

Creates a JSON-encoded text content response.

The data will be encoded to JSON using the built-in JSON module.

## Example

    def handle_call_tool(_conn, "get_user", %{"id" => id}) do
      user = MyApp.Users.get!(id)
      json(%{id: user.id, name: user.name, email: user.email})
    end

# `raw`
*macro* 

Returns raw data directly without MCP content wrapping.

This bypasses the standard MCP content structure and returns the data
as-is. Useful for debugging or special cases where you need direct
JSON output without the content array wrapper.

**Warning**: This breaks MCP compatibility and should only be used
for debugging or non-MCP endpoints.

## Example

    def handle_call_tool(_conn, "debug_user", %{"id" => id}) do
      user = MyApp.Users.get!(id)
      raw(%{id: user.id, name: user.name, email: user.email})
    end

    # Returns: {:ok, %{"id" => 123, "name" => "John", "email" => "john@example.com"}}
    # Instead of: {:ok, %{"content" => [%{"type" => "text", "text" => "{\"id\":123,...}"}]}}

# `raw_resource`
*macro* 

Creates a resource content response with a specified MIME type.

Useful for returning raw HTML, XML, or other content types from
resource `read` handlers. For MCP Apps `ui://` resources, prefer
`app_html/1` which uses the correct MIME type automatically.

## Example

    resource "config://settings.xml" do
      mime_type "application/xml"

      read fn _conn, _params, _opts ->
        xml = File.read!("priv/settings.xml")
        raw_resource(xml, "application/xml")
      end
    end

# `structured`
*macro* 

Returns a tool response with structured output.

Per MCP spec 2025-11-25, tools can declare an `outputSchema` and return
structured data alongside the human-readable `content` array. Clients
that understand the schema can render/validate the payload; clients
that don't fall back to the text content.

Accepts the structured payload (any JSON-encodable map) and an optional
human-readable message that becomes the text content. If you omit the
message, the JSON encoding of the payload is used.

## 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"}
        }
      }

      handle fn _conn, %{"id" => id} ->
        user = MyUsers.get!(id)
        structured(%{"id" => user.id, "email" => user.email}, "Fetched user #{id}")
      end
    end

# `system`
*macro* 

Creates a system role message for prompts.

## Example

    def handle_get_prompt(_conn, "assistant", _args) do
      {:ok, %{
        "messages" => [
          system("You are a helpful coding assistant")
        ]
      }}
    end

# `task`
*macro* 

Creates an MCP `tools/call` response that hands a long-running operation
off to a task.

The tool returns immediately with a `task_id`; the client polls
`tasks/get` / `tasks/result` to retrieve progress and the final result.
The task itself is tracked by `ConduitMcp.Tasks` — the tool author is
responsible for spawning the actual work (e.g., via `Task.Supervisor`)
and updating the task's status with `ConduitMcp.Tasks.update/2`.

Requires the tool's `task_support` to be `:supported` or `:required`.

## Example

    tool "render_video", "Render a video" do
      task_support :supported
      param :script, :string, "Script", required: true

      handle fn _conn, params ->
        task_id = ConduitMcp.Tasks.generate_id()
        {:ok, _} = ConduitMcp.Tasks.create(task_id, %{"tool" => "render_video"})

        Task.Supervisor.start_child(MyApp.Workers, fn ->
          result = MyRenderer.render(params)
          ConduitMcp.Tasks.update(task_id,
            %{"status" => "completed", "result" => result})
        end)

        task(task_id, "Rendering started")
      end
    end

# `text`
*macro* 

Creates a text content response.

## Example

    def handle_call_tool(_conn, "greet", %{"name" => name}) do
      text("Hello, #{name}!")
    end

# `texts`

Creates multiple text content items.

Useful for returning multiple pieces of content in a single response.

## Example

    def handle_call_tool(_conn, "analyze", params) do
      results = MyAnalyzer.run(params)

      {:ok, %{
        "content" => texts([
          "Analysis Results:",
          "Score: #{results.score}",
          "Details: #{results.details}"
        ])
      }}
    end

# `user`
*macro* 

Creates a user role message for prompts.

## Example

    def handle_get_prompt(_conn, "question", args) do
      {:ok, %{
        "messages" => [
          user("What is #{args["topic"]}?")
        ]
      }}
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
