ExMCP Troubleshooting Guide

View Source

stdio

Unexpected end of JSON input

The stdio transport requires stdout to contain only newline-delimited JSON-RPC. Avoid IO.puts/1, normal Logger output, or noisy startup scripts on stdout.

Use stderr for diagnostics:

IO.puts(:stderr, "debug")

For scripts with Mix.install/2, configure logging before installing deps:

Application.put_env(:ex_mcp, :stdio_mode, true)
Application.put_env(:logger, :level, :emergency)

Mix.install([{:ex_mcp, "~> 1.0.0-rc.1"}], verbose: false)

Server hangs after starting

Start stdio servers with:

MyServer.start_link(transport: :stdio)

For clients, command must be a list:

ExMCP.Client.start_link(transport: :stdio, command: ["node", "server.js"])

HTTP

Connection refused

Check the URL and endpoint path. If the path is included in url, ExMCP uses that as the default endpoint:

ExMCP.Client.start_link(transport: :http, url: "http://localhost:4000/mcp")

Or provide it explicitly:

ExMCP.Client.start_link(
  transport: :http,
  url: "http://localhost:4000",
  endpoint: "/mcp"
)

CORS errors

For Phoenix/Plug servers, configure CORS in your Plug pipeline or pass cors_enabled: true to ExMCP.HttpPlug.

SSE stream does not start

Confirm the server has SSE enabled and the client uses use_sse: true. Increase stream_handshake_timeout for slow deployments.

BEAM-Local

Client cannot connect

transport: :beam requires a live server PID:

{:ok, server} = MyServer.start_link(transport: :beam)  # DSL provides start_link; raw handlers use HandlerServer
Process.alive?(server)

{:ok, client} = ExMCP.Client.start_link(transport: :beam, server: server)

Do not use transport: :native; it was removed in the 1.0 API cleanup.

DSL

Tools do not appear

Use ExMCP.Server.Handler and ExMCP.Server.DSL together, and make sure the server starts through a supported transport:

defmodule MyServer do
  use ExMCP.Server.Handler
  use ExMCP.Server.DSL

  tool "ping", "Health check" do
    run fn _args, state ->
      {:ok, %{content: [%{type: "text", text: "pong"}]}, state}
    end
  end
end

Debugging

Enable debug logging for non-stdio transports:

Logger.configure(level: :debug)

Inspect local server state when using BEAM-local tests:

:sys.get_state(server)

Run focused tests:

mix test test/ex_mcp/client_beam_transport_test.exs
mix test test/ex_mcp/server/transport_test.exs