Sign in

    mcpbox

    A self-contained MCP client CLI for discovering and calling Model Context Protocol servers

    mcp
    model-context-protocol
    cli
    client
    agent
    moonbit
    Download zip
    Author
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    9 days ago
    Downloads
    40

    #mcpbox 📦 — carry MCP tools into scripts, CLIs and agents

    CI npm License

    mcpbox is a self-contained Model Context Protocol client and command line, written in MoonBit. It is for developers and coding agents that want the MCP tools they already have configured — from Cursor, Claude Code/Desktop, Windsurf, VS Code or an mcpbox file — available in a terminal, a script or a generated agent.

    It is deliberately small and stateless: one process, no daemon, no background service, no Node runtime at execution time.

    #Install

    Try it without installing anything:

    npx @tomasko/mcpbox --version

    Install it for repeated use:

    npm install -g @tomasko/mcpbox # or curl -LsSf https://raw.githubusercontent.com/tomasky/mcpbox/master/install.sh | bash # or, with the MoonBit toolchain: builds from source into ~/.moon/bin moon install kupas/mcpbox/cmd/mcpbox

    #Quick start

    List the servers mcpbox found, and the tools each one advertises:

    mcpbox list

    Call a tool. The first argument is <server>.<tool>; the rest are arguments:

    mcpbox call context7.resolve-library-id query="React hooks docs" libraryName=react

    Point it at a server you have not configured yet:

    mcpbox list --http-url https://mcp.context7.com/mcp mcpbox call adhoc.echo --stdio "python3 echo_server.py" name=hi count=5

    #What makes it pleasant

    • Schema-aware arguments. count=5 becomes the JSON number 5, flag=true a boolean and tags=a,b an array, using the tool's inputSchema. You only reach for JSON when you want to: meta:={"any":"json"}.
    • Parallel discovery. mcpbox list connects to every server at once, and a server that fails to start is reported without hiding the others.
    • Stable JSON. --json produces output meant to be parsed, while the default is meant to be read.
    • Real exit codes. A failed call exits non-zero, so a script or an agent can tell the difference.
    • Two transports. stdio child processes and Streamable HTTP, including text/event-stream responses and session ids.

    #Commands

    CommandPurpose
    mcpbox list [server ...]Discover servers and list their tools
    mcpbox call <server>.<tool> [key=value ...]Call a tool
    mcpbox configShow config files and the resolved server set
    mcpbox versionPrint the version
    mcpbox helpShow help

    Useful flags: --json, --brief, --raw, --timeout <seconds>, --http-url <url>, --stdio "<command>".

    #Configuration

    mcpbox reads the same mcpServers blocks the other MCP clients use, so an existing configuration works as-is. Files are JSONC: // and /* */ comments and trailing commas are accepted.

    A minimal mcpbox.json:

    { "mcpServers": { "context7": { "url": "https://mcp.context7.com/mcp" }, "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], "env": { "TOKEN": "${FS_TOKEN}" } } } }

    Files are read in precedence order, with the first definition of a name winning:

    1. $MCPBOX_CONFIG
    2. ./mcpbox.json, ./.mcpbox.json, ./config/mcpbox.json, ./.mcpbox/config.json
    3. ./.cursor/mcp.json, ./.vscode/mcp.json
    4. ~/.mcpbox.json
    5. ~/.config/mcpbox/config.json
    6. ~/.cursor/mcp.json, ~/.claude.json, ~/.codeium/windsurf/mcp_config.json, ~/.config/Claude/claude_desktop_config.json, ~/Library/Application Support/Claude/claude_desktop_config.json

    String values support ${VAR} and ${VAR:-fallback}. An unset variable with no fallback expands to the empty string.

    #Build from source

    moon build --target native --release _build/native/release/build/cmd/mcpbox/mcpbox.exe --help

    The release binaries are linked statically against musl on Linux, so they run on any distribution regardless of its glibc version.

    #Development

    moon check --target native --deny-warn moon test --target native moon info && moon fmt

    The stdio and HTTP transports are exercised end to end against the fixture server in tests/fixtures/echo_server.py:

    moon build --target native _build/native/debug/build/cmd/mcpbox/mcpbox.exe list \ --stdio "python3 tests/fixtures/echo_server.py"

    #License

    Apache-2.0

    McpError

    pub(all) suberror McpError {
    McpError(String)
    }

    The single error type the library raises. Everything user-facing — a bad config, an unreachable server, a failed tool — arrives here as a message that the CLI prints as-is.

    CallResult

    pub(all) struct CallResult {
    content : Array[Content]
    is_error : Bool
    structured : Json?
    } derive(
    Debug
    )

    The result of a tools/call.

    Content

    pub(all) enum Content {
    Text(String)
    Image(String, String)
    Audio(String, String)
    EmbeddedResource(Json)
    Unknown(Json)
    } derive(
    Debug
    )

    One block of a tool result. The first two kinds are the ones every client understands; the rest are kept verbatim so nothing is silently dropped.

    Content::to_repr

    HttpSpec

    pub(all) struct HttpSpec {
    url : String
    headers : Map[String, String]
    } derive(
    Debug
    )

    A Streamable HTTP endpoint, with any static headers the server needs.

    HttpSpec::to_repr

    RpcError

    pub(all) struct RpcError {
    code : Int
    message : String
    data : Json?
    } derive(
    Debug
    )

    A JSON-RPC 2.0 error object.

    RpcError::to_repr

    RpcMessage

    pub(all) enum RpcMessage {
    Response(Int, Json?, RpcError?)
    ServerRequest(Json, String, Json?)
    Notification(String, Json?)
    } derive(
    Debug
    )

    One decoded JSON-RPC message from a server.

    Server

    pub(all) struct Server {
    name : String
    kind : ServerKind
    note : String
    } derive(
    Debug
    )

    One configured MCP server.

    Server::to_repr

    ServerInfo

    pub(all) struct ServerInfo {
    name : String
    version : String
    protocol_version : String
    instructions : String
    } derive(
    Debug
    )

    What a server reports about itself during initialize.

    ServerKind

    pub(all) enum ServerKind {
    Stdio(StdioSpec)
    Http(HttpSpec)
    } derive(
    Debug
    )

    The transport half of a server definition.

    Session

    pub struct Session {
    // private fields
    }

    A live, initialized MCP session.

    Session::call_tool

    async fn Session::call_tool(self : Session, name : String, arguments : Json, timeout_ms : Int) -> CallResult

    Call one tool. arguments is the already-built JSON object.

    Session::list_tools

    async fn Session::list_tools(self : Session, timeout_ms : Int) -> Array[Tool]

    List the server's tools.

    Session::name

    fn Session::name(self : Session) -> String

    The server's name, for messages.

    Session::server_info

    fn Session::server_info(self : Session) -> ServerInfo

    What the server reported during initialize.

    StdioSpec

    pub(all) struct StdioSpec {
    command : String
    args : Array[String]
    env : Map[String, String]
    cwd : String
    } derive(
    Debug
    )

    How mcpbox reaches a stdio server: a child process that speaks newline-delimited JSON-RPC on its standard input and output.

    Tool

    pub(all) struct Tool {
    name : String
    title : String
    description : String
    input_schema : Json
    } derive(
    Debug
    )

    A tool advertised by a server's tools/list.

    Tool::to_repr

    build_arguments

    fn build_arguments(pairs : Array[String], schema : Json) -> Json raise McpError

    Build the arguments object for a tool call.

    call_result_to_json

    fn call_result_to_json(result : CallResult) -> Json

    The JSON view of a tool call result.

    coerce_value

    fn coerce_value(raw : String, schema : Json?) -> Json

    Coerce a key=value argument using the tool's schema. This is where mcpbox improves on a plain string passthrough: count=5 becomes a JSON number and tags=a,b a JSON array when the schema says so, so callers do not have to hand-write JSON for ordinary values. Anything that does not parse falls back to the string, and key:=<json> is always available for the exact form.

    config_files

    async fn config_files() -> Array[(String, Int)] raise McpError

    The config files that exist on this machine, in precedence order, with the number of servers each contributes. Used by mcpbox config.

    content_to_json

    fn content_to_json(content : Content) -> Json

    The JSON view of one content block, in the shape MCP uses.

    decode_rpc_message

    fn decode_rpc_message(json : Json) -> RpcMessage? raise McpError

    Decode one JSON-RPC message. Returns None for a well-formed object that is neither a response nor a notification mcpbox cares about, so the caller can simply skip it.

    expand_env

    fn expand_env(text : String) -> String

    Expand ${VAR} and ${VAR:-fallback} in a string. An unset variable with no fallback expands to the empty string rather than failing, so a missing optional token does not make the whole config unreadable.

    fail

    fn fail(message : String) -> McpError

    Raise a McpError with a formatted message.

    first_line

    fn first_line(text : String) -> String

    The first line of a description, so a table stays one line per tool.

    help_text

    fn help_text() -> String

    The mcpbox help text.

    load_servers

    async fn load_servers() -> Array[Server] raise McpError

    Load every server mcpbox can find. Names are deduplicated with the first definition winning, matching the precedence of config_candidates.

    main_cli

    async fn main_cli() -> Unit

    Entry point for the mcpbox executable. A failure is reported once, on stderr, and turns into a non-zero exit status.

    mcpbox_exit

    fn mcpbox_exit(code : Int) -> Unit

    Terminate the process with the given status. Used once the CLI has printed its result so scripts and agents see a meaningful exit code.

    parse_call_result

    fn parse_call_result(result : Json) -> CallResult raise McpError

    Decode a tools/call result.

    parse_content

    fn parse_content(json : Json) -> Content

    Decode one content block.

    parse_jsonc

    fn parse_jsonc(text : String) -> Json raise McpError

    Parse JSONC text into a Json value.

    parse_tool

    fn parse_tool(json : Json) -> Tool raise McpError

    Decode one tool descriptor, tolerating servers that omit optional fields.

    parse_tools

    fn parse_tools(result : Json) -> Array[Tool] raise McpError

    Decode a tools/list result.

    protocol_version

    let protocol_version : String

    The MCP revision mcpbox advertises first. Servers that predate it answer with the revision they do support, and mcpbox accepts whatever comes back: the wire format is unchanged across the revisions in use.

    render_call_result

    fn render_call_result(result : CallResult) -> String

    The text a human sees for a tool call: the text blocks joined, images and audio summarised, and structured content printed as JSON.

    render_params

    fn render_params(schema : Json) -> String

    Render a tool's parameters as name: type, optional?: type, with the names sorted so the output is stable across runs.

    render_tool_signature

    fn render_tool_signature(tool : Tool) -> String

    A one-line signature for a tool: name(params).

    rpc_error_response

    fn rpc_error_response(id : Json, code : Int, message : String) -> Json

    Build a JSON-RPC error response, echoing the request's id.

    rpc_notification

    fn rpc_notification(method_name : String, params : Json?) -> Json

    Build a JSON-RPC 2.0 notification: a request with no id, which the server must not answer.

    rpc_request

    fn rpc_request(id : Int, method_name : String, params : Json?) -> Json

    Build a JSON-RPC 2.0 request object. The repr keeps ids integral in the encoded form instead of printing them as 1.0.

    rpc_result_response

    fn rpc_result_response(id : Json, result : Json) -> Json

    Build the response to a server-initiated request, echoing its id.

    run

    async fn run(args : Array[String]) -> Unit

    The dispatch table.

    schema_properties

    fn schema_properties(schema : Json) -> Map[String, Json]

    The properties map of a tool schema, if any.

    schema_type

    fn schema_type(schema : Json) -> String

    The JSON Schema type of a property, rendered for a signature.

    server_info_to_json

    fn server_info_to_json(info : ServerInfo) -> Json

    The JSON view of a server's initialize result.

    server_to_json

    fn server_to_json(server : Server) -> Json

    The JSON view of a server definition.

    servers_from_document

    fn servers_from_document(json : Json, key : String, source : String) -> Array[Server] raise McpError

    Extract the servers from one parsed config document. Returns an empty array when the file has no server block, so an unrelated mcp.json is skipped rather than reported as broken.

    tool_to_json

    fn tool_to_json(tool : Tool) -> Json

    The JSON view of a tool.

    transport_label

    fn transport_label(server : Server) -> String

    A short transport label, e.g. http or stdio.

    transport_target

    fn transport_target(server : Server) -> String

    Where a server connects to, for a listing.

    version

    let version : String

    The mcpbox version, kept in sync with moon.mod and npmjs/package.json. It is also what the client reports to servers during initialize.

    with_server

    async fn with_server(server : Server, timeout_ms : Int, f : async (Session) -> Unit) -> Unit

    Connect to a server, run the initialize handshake, hand the session to f, and tear the transport down again. The server's child process is owned by the enclosing task group, so an error anywhere still reaps it.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io