Caravela ships a Model Context Protocol server that exposes the compiled domain as a set of tools an LLM host can call. With the server running, Claude Code / Cursor / Zed / any MCP-compliant host stops guessing the DSL and reads the IR directly.

Launching

mix caravela.mcp

The task compiles the project (so domain modules are loadable), then speaks JSON-RPC 2.0 over stdio, one message per line. Protocol version 2024-11-05.

No daemon, no port. The host owns process lifecycle — typically spawning one per workspace.

Claude Code / Claude Desktop

// ~/.claude/claude_desktop_config.json
{
  "mcpServers": {
    "caravela": {
      "command": "mix",
      "args": ["caravela.mcp"],
      "cwd": "/absolute/path/to/your/project"
    }
  }
}

Restart the host; Caravela's tools appear under the project's MCP server. Other MCP-compliant clients take a similar {command, args, cwd} shape.

Tool surface

Every tool name is prefixed caravela__ so multiple MCP servers can coexist without collision. Inputs / outputs are JSON maps matching the IR schema (Caravela.IR).

ToolInputReturns
caravela__describe_domain{domain: "MyApp.Domains.X"}Full IR: entities, fields, relations, policies, auth, hooks
caravela__list_entities{domain}Entity names declared on the domain, in DSL order
caravela__describe_entity{domain, entity: "books"}One entity's IR plus its relations (inbound + outbound)
caravela__describe_frontend_mode{domain, entity?}Render transport (:live / :rest), realtime? flag, per-mode notes
caravela__validate_dsl{source: "defmodule …"}{:ok} or {:error, [DSLError]} — parse a candidate domain file without compiling it into the project

All tools read from the current compiled IR. Modify the DSL, recompile, and subsequent tool calls see the new shape. No caching.

Why use it

An LLM editing a Caravela app without MCP guesses at module names, DSL spellings, and policy shapes from whatever it saw in its training. With MCP it reads the actual IR of the actual project, and its suggestions reference real entities / fields / relations. Concretely:

  • No hallucinated function names. The LLM sees that MyApp.Library.list_books/1 exists because caravela__describe_entity returns its public API.
  • No guessed render mode. Asked "add a delete button to BookIndex", the LLM first calls caravela__describe_frontend_mode to confirm whether books is served as :live (live.pushEvent) or :rest (useForm / navigate) — those have different client patterns.
  • Pre-validated DSL changes. Before proposing a DSL patch, the LLM can call caravela__validate_dsl on the candidate source and catch errors without the user hitting compile in a loop.

Context for the rest of the plan

The MCP server is §9 of llm_friendliness.md and composes with the other LLM-grounding deliverables:

  • Structured errors (§4 — shipped v0.9.0): every Caravela.DSLError carries :message, :snippet, :suggestion, and :docs_url. caravela__validate_dsl returns them verbatim.
  • Unified check (mix caravela.check — shipped v0.9.1): one oracle for "is this correct?" — hosts that want a pass/fail instead of per-tool inspection call this.
  • JSON IR (mix caravela.ir + Caravela.IR.of/1 — shipped v0.9.0): the same structure the MCP tools return, dumpable to a file for grounding RAG systems or fine-tuning corpora.

The eval harness, mix caravela.new --from "<prompt>", and the cookbook are still open on that plan; see llm_friendliness.md for the roadmap.

Security

The stdio transport shipped in Caravela 0.10+ is local-only. An HTTP transport for team / shared MCP scenarios is out of 1.1 scope and will require explicit auth. Write-capable tools (tools that would modify files rather than just read the IR) are also out of scope until after 1.0 — the current tool surface is read-only plus one sandbox validator.