mcp-inspector-cli

One-shot command-line MCP client for the 2026-07-28 protocol era.

mcp
model-context-protocol
cli
inspector
moon add marianoguerra/mcp-inspector-cli@0.7.0
Download zip
Version
0.7.0
License
MIT
Last updated
2 days ago
Downloads
21
README

#marianoguerra/mcp-inspector-cli

A one-shot command-line client for MCP 2026-07-28 servers, modelled on mcp-inspector --cli from the reference implementation.

moon install marianoguerra/mcp-inspector-cli mcp-inspector-cli --help

mcp-inspector-cli http://127.0.0.1:3100/mcp --transport http --method tools/list mcp-inspector-cli ./my-server --transport stdio --method tools/call \ --tool-name echo --tool-arg message=hi mcp-inspector-cli --config ./mcp.json --server my-server --method tools/list

--tool-arg values are JSON-coerced, so count=1 is a number and name=hi a string. Output is pretty-printed JSON, or a single-line {"result": …} envelope with --format json.

Exit codes: 0 ok · 1 usage/error · 2 no app · 3 auth required · 4 unreachable · 5 tool error. Every non-zero exit also writes one JSON line to stderr: {"error":{"code":…,"message":…,"status":…,"url":…}}.

Modern era only. There is no legacy initialize handshake, so a 2025-era server — which today means every server in the official MCP monorepo — is rejected rather than fallen back to. See the repository README for the measured details.

#
CliExit

pub(all) suberror CliExit {
CliExit(code~ : Int, envelope~ : ErrorEnvelope)
} derive(
Debug
)

Raised to request a specific exit code without going through classification.

#
UsageError

pub(all) suberror UsageError {
UsageError(String)
} derive(
Debug
)

Raised for anything that makes the command line unusable. Maps to exit 1.
impl Show for UsageError

#
Args

pub struct Args {
target : Array[String]
multi : Map[String, Array[String]]
single : Map[String, String]
flags : Map[String, Bool]
}

Everything the CLI was asked to do.

#
Args::flag

fn Args::flag(self : Args, name : String) -> Bool

#
Args::list

fn Args::list(self : Args, name : String) -> Array[String]

#
Args::opt

fn Args::opt(self : Args, name : String) -> String?

#
ErrorEnvelope

pub(all) struct ErrorEnvelope {
code : String
message : String
cause : String?
status : Int?
url : String?
} derive(
Debug
)

A failure classified for a programmatic caller.

#
ErrorEnvelope::to_json

fn ErrorEnvelope::to_json(self : ErrorEnvelope) -> Json

#
Target

type Target

Where to connect, resolved from the target and flags.

#
classify

fn classify(e : Error, url? : String) -> (Int, ErrorEnvelope)

Map an error onto an exit code and envelope.

Classification is structural where it can be: a TransportError::Network is unreachable because of what it is, not because its text matched a regex.

#
collect_kv

fn collect_kv(pairs : Array[String]) -> Map[String, Json] raise UsageError

Collect repeated key=value options into a JSON object.

#
collect_meta

fn collect_meta(pairs : Array[String]) -> Map[String, Json] raise UsageError

Metadata values are carried as strings: a non-string coerced value is re-encoded rather than stringified structurally, so {"a":1} stays JSON and not "[object ...]".

#
emit_error

async fn emit_error(envelope : ErrorEnvelope) -> Unit

Write the one-line error envelope to stderr.

#
emit_result

async fn emit_result(result : Json, format : String, app_info? : Json) -> Unit

Write the result to stdout in the requested format.

text is pretty-printed JSON, json is a single-line {"result": ...} envelope so the whole output pipes into jq.

#
exit_auth_required

let exit_auth_required : Int

The server requires authentication.

#
exit_no_app

let exit_no_app : Int

--app-info found no MCP App on the tool.

#
exit_ok

let exit_ok : Int

#
exit_tool_error

let exit_tool_error : Int

A tool returned isError:true, or the named tool does not exist.

#
exit_unreachable

let exit_unreachable : Int

The server is unreachable.

#
exit_usage

let exit_usage : Int

Usage or unexpected error -- the catch-all.

#
parse_args

fn parse_args(argv : Array[String]) -> Args raise UsageError

Parse argv (without the program name).

The target/option split follows the reference: with an explicit --, everything BEFORE it is the target and everything after is options; otherwise the target is the leading run of tokens that do not start with -. That ordering is what lets inspector-cli node server.js --method tools/list work without quoting.

#
parse_header

fn parse_header(pair : String) -> (String, String) raise UsageError

Parse a Name: Value header pair. Split on the FIRST colon so a URL in the value survives.

#
parse_kv

fn parse_kv(pair : String) -> (String, Json) raise UsageError

Parse a key=value pair, JSON-coercing the value.

This is what makes --tool-arg count=1 a number and --tool-arg name=hello a string: the value is offered to the JSON parser first and falls back to a string.