# `Tama.Dex.MCP`
[🔗](https://github.com/kritama/tama-dex/blob/v0.1.0/lib/tama/dex/mcp.ex#L1)

The `search` and `execute` MCP tools, as definitions and handlers.

tama-dex runs no MCP server and doesn't depend on one. A host's server
advertises two tools built from `search_tool/0` and `execute_tool/0`, and
calls `handle_search/3` and `handle_execute/4` from them. Each definition
is a map with `name`, `title`, `description`, `input_schema` and
`output_schema` (JSON Schema 2020-12 maps with string keys) and
`annotations` (MCP tool hints).

## Callers

A caller is any map or struct whose `scopes` field holds the caller's
verified scope list, such as `TamaMCP.Context`. The handlers check every
operation against those scopes, so a host's own scope on the `search` or
`execute` tool is only an outer gate, never per-operation authorization.
`handle_execute/4` passes the whole caller to the executor as
`call.context`.

## Responses

Handlers return JSON-safe maps with string keys, matching the tool's
output schema, for the host to send as structured content:

- `search`: `%{"results" => results, "truncated" => boolean}`.
- `execute`: `%{"cassette", "operation_id", "source", "result"}`; or, for
  the executor's own valid error,
  `%{"cassette", "operation_id", "source", "operation_error", "dispatched" => true}`.
- Either tool, on failure: `%{"error" => %{"kind", "dispatched", "message"}}`,
  with `"details"` when the kind has any. See `Tama.Dex.Error` for the
  kinds `execute` returns. Arguments that don't match the tool's input
  schema give `"invalid_arguments"` before any lookup.

An error with `"dispatched" => true` means the operation may have taken
effect: reconcile its state rather than retrying.

## A TamaMCP host

`TamaMCP.Tool` takes literal schemas, so a host builds its two tool
modules with a macro that injects the definitions at compile time:

    defmodule MyHost.DexTool do
      defmacro __using__(opts) do
        tool = apply(Tama.Dex.MCP, Keyword.fetch!(opts, :tool), [])

        quote do
          use TamaMCP.Tool,
            scopes: unquote(Keyword.fetch!(opts, :scopes)),
            title: unquote(tool.title),
            description: unquote(tool.description),
            annotations: unquote(Map.to_list(tool.annotations))

          raw_input_schema(unquote(Macro.escape(tool.input_schema)))
          raw_output_schema(unquote(Macro.escape(tool.output_schema)))
        end
      end

      def respond(structured) do
        failed? = Map.has_key?(structured, "error") or Map.has_key?(structured, "operation_error")
        build = if failed?, do: &TamaMCP.Response.tool_error/1, else: &TamaMCP.Response.success/1

        {:ok,
         build.(
           structured_content: structured,
           content: [TamaMCP.Response.text(Jason.encode!(structured))]
         )}
      end
    end

    defmodule MyHost.Tools.Search do
      use MyHost.DexTool, tool: :search_tool, scopes: ["ops.search"]

      @impl TamaMCP.Tool
      def call(arguments, context) do
        MyHost.Dex
        |> Tama.Dex.MCP.handle_search(arguments, context)
        |> MyHost.DexTool.respond()
      end
    end

    defmodule MyHost.Tools.Execute do
      use MyHost.DexTool, tool: :execute_tool, scopes: ["ops.execute"]

      @impl TamaMCP.Tool
      def call(arguments, context) do
        MyHost.Dex
        |> Tama.Dex.MCP.handle_execute(arguments, context, MyHost.executors())
        |> MyHost.DexTool.respond()
      end
    end

The server registers them as `tool MyHost.Tools.Search, name: "search"` and
`tool MyHost.Tools.Execute, name: "execute"`.

# `caller`

```elixir
@type caller() :: %{:scopes =&gt; [String.t()], optional(atom()) =&gt; term()}
```

# `definition`

```elixir
@type definition() :: %{
  name: String.t(),
  title: String.t(),
  description: String.t(),
  input_schema: map(),
  output_schema: map(),
  annotations: %{required(atom()) =&gt; boolean()}
}
```

# `execute_tool`

```elixir
@spec execute_tool() :: definition()
```

The `execute` tool definition.

# `handle_execute`

```elixir
@spec handle_execute(Tama.Dex.Index.t(), term(), caller(), map()) :: map()
```

Handles an `execute` tool call: runs `Tama.Dex.execute/5` with the
caller's scopes, the host's `executors` (see `Tama.Dex.Executor`) and the
caller as `call.context`.

Returns the result, the executor's own valid error with
`"dispatched" => true`, or an `"error"` object. Raises `ArgumentError`
when `caller` has no `scopes` list or `executors` isn't a map.

# `handle_search`

```elixir
@spec handle_search(Tama.Dex.Index.t(), term(), caller()) :: map()
```

Handles a `search` tool call: searches the operations the caller's scopes
allow, using the tool's arguments.

Returns `%{"results" => results, "truncated" => boolean}`, with the result
fields of `Tama.Dex.search/3`, or an `"error"` object. Raises
`ArgumentError` when `caller` has no `scopes` list.

# `search_tool`

```elixir
@spec search_tool() :: definition()
```

The `search` tool definition.

---

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