Cairnloop.Web.MCP.ToolProjector (cairnloop v0.1.0)

Copy Markdown View Source

Pure total function transform: %Cairnloop.Tool.Spec{} + tool module → MCP tool definition map.

No DB, no side effects, no supervision. Host opts in by mounting Cairnloop.Web.MCP.Router (D17-08) — this module is called by the Router's tools/list handler.

MCP tool definition shape

Produces a plain map with string keys matching the MCP 2025-03-26 Tool object:

%{
  "name"        => "Elixir.Cairnloop.Tools.InternalNote",
  "title"       => "Add internal note",
  "description" => "Appends an operator-only note to the conversation thread.",
  "inputSchema" => %{
    "type"       => "object",
    "properties" => %{
      "conversation_id" => %{"type" => "string"},
      "content"         => %{"type" => "string"}
    },
    "required" => ["conversation_id", "content"]
  },
  "x-cairnloop-risk-tier"     => "low_write",
  "x-cairnloop-approval-mode" => "requires_approval"
}

inputSchema derivation

inputSchema is derived by calling tool_module.changeset(struct(tool_module), %{}) with empty attrs. This yields:

  • cs.required — list of required field atoms from validate_required/2
  • cs.types — map of field atom → Ecto type atom

The :id auto-generated field is excluded from properties (Pitfall 2 from RESEARCH.md). Ecto types are mapped to JSON Schema type strings via a minimal safe mapping table.

Summary

Functions

Projects a {tool_module, %Cairnloop.Tool.Spec{}} tuple to an MCP tool definition map.

Functions

spec_to_mcp(arg)

@spec spec_to_mcp({module(), Cairnloop.Tool.Spec.t()}) :: map()

Projects a {tool_module, %Cairnloop.Tool.Spec{}} tuple to an MCP tool definition map.

Returns a plain map with string keys. All values are JSON-safe.