Inspector (mix mcp.client)

Copy Markdown View Source

mix mcp.client launches Noizu.MCP.Inspector — a localhost-only single-page HTML client for exploring and exercising MCP servers interactively. It covers the same ground as the official mcp dev / MCP Inspector tool but runs inside your Elixir project with no separate install.

It supports three target modes:

  • In-process module — connects directly to a use Noizu.MCP.Server module running in the same VM (no subprocess, no HTTP).
  • Stdio subprocess — spawns an arbitrary command as a stdio MCP server.
  • Remote Streamable HTTP — connects to any MCP server over HTTP.

Requires the optional :bandit and :plug dependencies (add :req for --url targets).

Dependencies

# mix.exs (dev only)
{:bandit, "~> 1.5", only: :dev},
{:plug,   "~> 1.16", only: :dev},

# add this too if you need --url targets:
{:req, "~> 0.5", only: :dev}

Quickstart

No target — pick one in the browser

mix mcp.client

Launches the inspector with no connection; the Connection tab offers a target picker — a dropdown of MCP server modules discovered in the running VM (any module exporting __mcp__/1 from applications depending on :noizu_mcp), plus stdio-command and HTTP-URL forms. You can switch targets at any time from the same panel; switching tears down the old session and resets every tab.

In-process server module

The most common use case during development — connects directly without spawning any subprocess:

mix mcp.client MyApp.MCP

The inspector starts on port 6274 (default), prints a tokenized URL, and opens your browser automatically:

MCP inspector running at:

    http://127.0.0.1:6274/?token=<random>

Target: in-process MyApp.MCP
Press Ctrl-C to stop.

External stdio server

mix mcp.client --stdio "npx -y @modelcontextprotocol/server-everything"

# with working directory and environment variables
mix mcp.client --stdio "node server.js" \
  --cd /path/to/server \
  --env NODE_ENV=development \
  --env DEBUG=mcp

Remote Streamable HTTP server

mix mcp.client --url http://localhost:4040/mcp

# with a bearer token
mix mcp.client --url https://api.example.com/mcp --bearer my-token

Options

FlagDefaultDescription
--port PORT6274HTTP port; 0 picks a random free port
--no-openSkip auto-opening the browser
--stdio CMDSpawn CMD (shell-split) as a stdio server
--cd DIRWorking directory for the stdio subprocess
--env K=VEnvironment variable for stdio subprocess (repeatable)
--url URLStreamable HTTP target URL
--bearer TOKENBearer token for --url targets
--name NAMEnoizu-mcp-inspectorAdvertised client name
--version VSNlibrary versionAdvertised client version

UI Tab Tour

Connection

Shows server info, negotiated capabilities, and server instructions. Displays the connection target descriptor. Provides a Config export button that generates a claude_desktop-style config entry for the current target.

Tools

Lists every tool exposed by the server. Each tool shows its description and a form generated from the JSON Schema input definition. Fill in the fields and click Run — progress appears inline and a Cancel button is available for long-running calls. Results appear below the form.

Resources

Lists resources and resource templates. You can read a resource URI directly, subscribe/unsubscribe to change notifications, and expand RFC 6570 templates with arguments; completion suggestions are requested from the server as you type.

Prompts

Lists prompts with their argument schemas. Fill in arguments (with server-side completion) and preview the rendered message list.

History

Scrollable log of raw JSON-RPC frames in both directions — useful for debugging protocol issues or verifying request/response structure.

Notifications

Real-time log of notifications/* messages from the server (resource updates, list changes, log messages). Filterable by log level.

Pending

When the server calls back into the client (sampling or elicitation), the request appears here and blocks until you answer. See the sampling and elicitation walkthrough below.

Sampling and Elicitation Walkthrough

Some MCP servers call back into the client mid-tool-call to request LLM inference (sampling) or human input (elicitation). The inspector handles both without requiring you to write a handler.

  1. Trigger a tool call that initiates a sampling or elicitation request.
  2. The Pending tab badge increments; the tool call stays in-progress.
  3. Open the Pending tab. Each parked request shows its kind and params.
  4. For sampling: review the message list and model preferences, fill in a response message, and click Submit.
  5. For elicitation: review the prompt and schema, fill in the fields (or click Decline / Cancel), then click Accept.
  6. The server call unblocks and completes.

Tool calls run with infinite timeout while a pending request is parked, so there is no race between human think-time and server timeouts.

Things Worth Poking

  • Progress + cancellation — call a slow tool with the Tools tab; watch inline progress and hit Cancel mid-run.
  • Hidden tools — items registered hidden: true won't appear in the tool list, but you can still invoke them by name. A registered Noizu.MCP.Server.Tools.Catalog tool returns full definitions of everything, hidden included — see Toolkits, Categories & Hidden Tools.
  • Validation behaviour — send arguments that violate the input schema and confirm you get an isError: true result (SEP-1303), not a protocol error.
  • Subscriptions — subscribe to a subscribable resource in the Resources tab, then trigger MyApp.MCP.notify_resource_updated/1 from an IEx shell and watch the update arrive in the Notifications tab.

Security Notes

  • The inspector binds exclusively to 127.0.0.1. It never listens on external interfaces.
  • A random 256-bit bearer token is generated per run and required on every /api request. Without the token in the URL, the UI cannot load.
  • The SSE bridge validates the Origin header to localhost to prevent cross-origin access from other browser tabs.
  • Browser-supplied target descriptors for module connections only accept already-loaded atoms — the server never calls String.to_atom/1 on untrusted input.
  • Frames larger than 64 KB are truncated in the History tab (a preview and byte count are shown instead).

Programmatic Embedding

mix mcp.client is a thin wrapper. You can start Noizu.MCP.Inspector directly from a supervision tree or a Mix task of your own:

token = Base.url_encode64(:crypto.strong_rand_bytes(32), padding: false)

{:ok, _pid} =
  Noizu.MCP.Inspector.start_link(
    token:   token,
    port:    6274,
    # optional:
    target:      {:module, MyApp.MCP}, # omit to pick a target in the browser
    name:        MyApp.Inspector,      # OTP name; defaults to Noizu.MCP.Inspector
    client_info: %{name: "myapp-inspector", version: "1.0.0"}
  )

url = "#{Noizu.MCP.Inspector.url(MyApp.Inspector)}?token=#{token}"

Other target forms:

# stdio subprocess
target: {:stdio, "node server.js",
                 args: ["--port", "9000"],
                 env: %{"NODE_ENV" => "dev"},
                 cd: "/path/to/server"}

# remote Streamable HTTP
target: {:url, "https://api.example.com/mcp", bearer: "my-token"}
target: {:url, "https://api.example.com/mcp",
               headers: [{"authorization", "Bearer my-token"}]}

Noizu.MCP.Inspector.port/1 and Noizu.MCP.Inspector.url/1 accept the OTP name passed as :name (or default to Noizu.MCP.Inspector). With port: 0 the OS picks a free port; call Noizu.MCP.Inspector.port/1 after startup to retrieve it.

How It Works

Noizu.MCP.Inspector is a Supervisor with three children:

Each browser connection creates an Inspector.Session GenServer that owns a Noizu.MCP.Client wrapped in Inspector.TapTransport. The tap mirrors every raw JSON-RPC frame to the session process, which fans them out to browser SSE subscribers (History tab). Server-initiated sampling and elicitation requests are intercepted by Inspector.Handler, which parks them in the session until the browser responds.

Fast operations (list tools, read resource, get prompt) bypass the session process and call Noizu.MCP.Client directly — a slow tool call cannot delay event fan-out or other requests.

The SSE stream carries seven event types: frame, notification, progress, call_result, pending_request, pending_resolved, and status. A 500-event ring buffer supports Last-Event-ID reconnect replay.

Using the Official MCP Inspector

If you prefer the upstream MCP Inspector (npx @modelcontextprotocol/inspector), it works with noizu_mcp servers too. For stdio targets, compile first so Mix output doesn't corrupt the stream (mix compile, then point the Inspector at mix run --no-halt). For Streamable HTTP targets, the default origins: :localhost setting of Noizu.MCP.Transport.StreamableHTTP.Plug admits the locally running Inspector — see Streamable HTTP if you need to adjust allowed origins. For CI and scripted checks, prefer Noizu.MCP.Test helpers — see Testing.