Sign in

    moonmcp

    Model Context Protocol servers for MoonBit.

    mcp
    llm
    agent
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    yesterday
    Downloads
    35

    #moonmcp

    Model Context Protocol servers for MoonBit. Register tools, resources, resource templates and prompts; serve them over Streamable HTTP or stdio.

    One server answers both eras of the protocol. A request that carries its protocol version in _meta is served statelessly under revision 2026-07-28; an initialize handshake selects the revisions before it (2025-11-25 back to 2024-11-05). Clients of either era connect without configuration.

    #Quick start

    async fn main {
    let mcp = @moonmcp.Server::new("demo", version="1.0.0")
    mcp.tool(
    "add",
    args => {
    guard args.get("a") is Some(Number(a, ..)) &&
    args.get("b") is Some(Number(b, ..)) else {
    fail("a and b must be numbers")
    }
    { "sum": Json(a + b) }
    },
    description="Add two numbers",
    input_schema={
    "type": "object",
    "properties": { "a": { "type": "number" }, "b": { "type": "number" } },
    "required": ["a", "b"],
    },
    )
    mcp.resource("config://app", () => "{\"theme\":\"dark\"}", mime_type="application/json")
    @mooncat.serve(mcp.to_asgi(), port=12000) catch {
    e => println("stopped: \{e}")
    }
    }

    The server is now at http://127.0.0.1:12000/mcp. For a client that starts the server as a subprocess, call mcp.run_stdio() instead of serving HTTP.

    moon.mod needs moonbitstack/moonmcp, moonbitstack/mooncat and moonbitlang/async, and the package with main imports the same three.

    #Registering

    CallOffers
    tool(name, handler, input_schema=, output_schema=, description=, annotations=)A tool. The handler returns any JSON value: an object becomes structured content as well as text.
    resource(uri, read, mime_type=)A resource read as text.
    template(uri_template, read)Every resource an RFC 6570 template names; read gets the matched parameters.
    prompt(name, render, arguments=)A prompt rendered as one user message.
    add_tool, add_resource, add_template, add_promptA component built in full with Tool::new, Resource::new, ResourceTemplate::new or Prompt::new.

    Arguments are checked against the tool's input schema (JSON Schema 2020-12) before the handler runs. A wrong argument or an error the handler raises comes back as a result marked isError, which the model reads and can correct itself from; only a tool that does not exist is a protocol error.

    #Annotations

    The same registrations, written on the functions themselves:

    #mcp.tool(description="Add two numbers")
    fn add(a : Double, b : Double) -> Double {
    a + b
    }

    #mcp.tool(name="word_count")
    fn count(text : String, unique~ : Bool = false) -> Int {
    ...
    }

    #mcp.resource("users://{id}/profile")
    fn profile(id : Int) -> String {
    "profile of user \{id}"
    }

    #mcp.prompt(description="Ask for a code review")
    fn review(code : String, focus? : String) -> String {
    ...
    }

    Each parameter becomes a property of the tool's input schema, typed from its MoonBit type; optional parameters and those with a default are left out of required. A URI with {name} makes a resource template whose matched values are read into the function's parameters. A tool's or resource's result goes back as JSON unless it is a String.

    Wire the generator into the build once. In moon.mod:

    rule(name: "mcp", command: "moonx moonbitstack/moonmcp/cmd/mcp@0.1.1 -C \"$mod_dir\" \"$input\" -o \"$output\"")

    and in the moon.pkg of the package holding the functions:

    dev_build(rule: "mcp", input: "tools.mbt", output: "tools.g.mbt") formatter(ignore: [ "tools.g.mbt" ])

    moon check, moon build and moon run regenerate tools.g.mbt whenever tools.mbt changes. It holds register(mcp), which the program calls once before serving.

    #Transports

    TransportCallNotes
    Streamable HTTPto_asgi(path="/mcp", origins=[])An ASGI application. One POST per message, answered with one JSON object or 202. Version, method and name headers are checked against the body; a foreign Origin is refused with 403.
    stdiorun_stdio()One message per line on standard input and output. Native.
    Any line streamserve_lines(read, write)The stdio loop over streams of your own.
    In processhandle(message)One JSON-RPC message in, the reply out; None for a notification.

    Under revision 2026-07-28 every result carries resultType and the server's identity; list results also carry ttlMs and cacheScope, set with Server::new(ttl_ms=, scope=).

    #Examples

    ExampleShows
    demoOne server with two tools, a resource, a resource template and a prompt
    httpThat server over Streamable HTTP: moon run src/examples/http --target native
    stdioThat server over stdio: moon run src/examples/stdio --target native
    annotationsTools, resources, a template and a prompt written as #mcp.* annotations, served over stdio

    #Packages

    PackageWhat it holds
    moonmcpThe names a server is written with: Server, Tool, Resource, Prompt and the rest
    serverServer: registration, dispatch for both protocol eras, Streamable HTTP, stdio; decode, which reads an argument as a parameter's type
    cmd/mcpThe generator behind #mcp.tool, #mcp.resource and #mcp.prompt
    typesThe wire model: content blocks, tools, prompts, resources, capabilities, results
    tools, resources, promptsThe components a server offers
    exceptionsFastMCPError and the MCP error codes it maps to
    component, tasks, settings, config, utilitiesComponent metadata, task configuration, settings, the mcp.json client configuration, media helpers

    #Install

    moon add moonbitstack/moonmcp

    #Licence

    Apache-2.0. See LICENSE.

    FastMCPError

    Custom errors for moonmcp, mirroring fastmcp/exceptions.py.

    Message

    A wrapper for a prompt message with a role.

    Prompt

    A prompt template that can be rendered with arguments.

    Resource

    A registered resource.

    ResourceTemplate

    A template for dynamically creating resources.

    Scope

    Who may keep a cached list result: any intermediary, or only caches inside the same authorisation context (the cacheScope of a cacheable result).

    Server

    An MCP server (← FastMCP's FastMCP): the tools, resources, resource templates and prompts it offers, and who it says it is.

    It speaks both eras of the protocol. A request carrying its protocol version in _meta is served statelessly under the 2026-07-28 revision; an initialize request opens the handshake of the revisions before it. The same server is reached over Streamable HTTP with to_asgi, over stdio with run_stdio, or by any transport of your own through handle.

    ttl_ms and scope are the caching hints every list result carries under the 2026-07-28 revision. The presets say "stale at once" and "this caller only", the cautious end of both, since a server that changes what it lists per caller would otherwise leak one caller's list to the next.

    Tool

    A registered tool.

    ToolResult

    The canonical result of running a tool.

    decode

    The argument name of a call, read as T; the code #mcp.tool generates calls this for each required parameter. A string read where something else is wanted is read again as JSON, which is how the {id} of a resource template, always text, reaches an Int parameter.

    decode_option

    fn[T :
    FromJson
    ] decode_option(args : Map[String, Json], name : String) -> T? raise
    JsonDecodeError

    The argument name of a call when it may be left out: None when it is missing or null, else the value read as decode reads it. The code #mcp.tool generates calls this for optional parameters.

    Source Files