Current section
Files
Jump to
Current section
Files
lib/mix/tasks/ex_claw.mcp.ex
defmodule Mix.Tasks.ExClaw.Mcp do
@moduledoc """
Starts ExClaw as an MCP server over stdio.
mix ex_claw.mcp [--skills-dir PATH]
Reads JSON-RPC 2.0 requests from stdin and writes responses to stdout.
Tools are loaded from the skills directory if provided.
"""
use Mix.Task
alias ExClaw.Skill.Loader, as: SkillLoader
alias ExClaw.MCP.Server
@shortdoc "Start ExClaw as an MCP server (stdio)"
@impl true
def run(args) do
Mix.Task.run("app.start")
{opts, _, _} = OptionParser.parse(args, strict: [skills_dir: :string, config: :string])
policy = load_tool_policy(opts[:config])
tools = load_tools(opts[:skills_dir]) |> ExClaw.ToolPolicy.filter(policy)
{:ok, pid} = Server.start_link(tools: tools)
# Block until the server exits
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, :process, ^pid, _reason} -> :ok
end
end
defp load_tool_policy(nil), do: nil
defp load_tool_policy(config_path) do
with {:ok, config} <- ExClaw.Config.load(config_path),
{:ok, policy} <- ExClaw.Config.tool_policy(config) do
policy
else
_ -> nil
end
end
defp load_tools(nil), do: []
defp load_tools(skills_dir) do
{:ok, skills} = SkillLoader.scan(skills_dir)
Enum.map(skills, fn skill ->
ADK.Tool.FunctionTool.new(skill.name,
description: skill.description,
func: fn _args -> {:ok, skill.instruction || ""} end,
parameters: %{"type" => "object", "properties" => %{}}
)
end)
end
end