defmodule Mix.Tasks.Hermes.Stdio.Interactive do @shortdoc "Test the STDIO transport implementation interactively." @moduledoc """ Mix task to test the STDIO transport implementation, interactively sending commands. ## Options * `--command` - Command to execute for the STDIO transport (default: "mcp") * `--args` - Comma-separated arguments for the command (default: "run,priv/dev/echo/index.py") """ use Mix.Task alias Hermes.Client alias Hermes.Transport.STDIO alias Mix.Interactive.Shell alias Mix.Interactive.UI @switches [ command: :string, args: :string, verbose: :count ] def run(args) do # Start required applications without requiring a project Application.ensure_all_started(:hermes_mcp) # Parse arguments and set log level {parsed, _} = OptionParser.parse!(args, strict: @switches, aliases: [c: :command, v: :verbose] ) verbose_count = parsed[:verbose] || 0 log_level = get_log_level(verbose_count) configure_logger(log_level) cmd = parsed[:command] || "mcp" args = String.split(parsed[:args] || "run,priv/dev/echo/index.py", ",", trim: true) header = UI.header("HERMES MCP STDIO INTERACTIVE") IO.puts(header) IO.puts("#{UI.colors().info}Starting STDIO interaction MCP server#{UI.colors().reset}\n") if cmd == "mcp" and not (!!System.find_executable("mcp")) do IO.puts( "#{UI.colors().error}Error: mcp executable not found in PATH, maybe you need to activate venv#{UI.colors().reset}" ) System.halt(1) end {:ok, _} = STDIO.start_link( command: cmd, args: args, client: :stdio_test ) IO.puts("#{UI.colors().success}✓ STDIO transport started#{UI.colors().reset}") {:ok, client} = Client.start_link( name: :stdio_test, transport: [layer: STDIO], client_info: %{ "name" => "Mix.Tasks.STDIO", "version" => "1.0.0" }, capabilities: %{ "roots" => %{ "listChanged" => true }, "sampling" => %{} } ) IO.puts("#{UI.colors().success}✓ Client connected successfully#{UI.colors().reset}") IO.puts("\nType #{UI.colors().command}help#{UI.colors().reset} for available commands\n") Shell.loop(client) end # Helper functions defp get_log_level(count) do case count do 0 -> :error 1 -> :warning 2 -> :info _ -> :debug end end defp configure_logger(log_level) do metadata = Logger.metadata() Logger.configure(level: log_level) Logger.metadata(metadata) end end