Define MCP 2026-07-28 servers: a side-effect-free routing core, tools/resources/prompts, and a transport trait. Depends only on marianoguerra/mcp.
Dependencies
moon add marianoguerra/mcp-server # define a server
moon add marianoguerra/mcp-server-native # and serve itlet srv = @server.new("weather", "1.0.0")
srv
..instructions("Weather lookups. Call get_forecast before booking anything outdoors.")
..tool(
"get_forecast",
(args, _ctx) => @server.structured({
"temperature": 22.5,
"conditions": Json::string("Partly cloudy in " + args.str("city")),
}),
description="Get the forecast for a city",
input_schema={
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"],
},
output_schema={
"type": "object",
"properties": {
"temperature": { "type": "number" },
"conditions": { "type": "string" },
},
"required": ["temperature", "conditions"],
},
)
..resource(
"readme",
"docs://readme",
(_uri, _ctx) => @server.resource_text("Ask about the weather.", mime_type="text/plain"),
description="How to use this server",
)
.prompt(
"plan-trip",
(args, _ctx) => @server.messages([
@server.user_text("Plan a trip to " + args.str("city")),
]),
arguments=[{ name: "city", description: Some("Destination"), required: true }],
)| server/discover | answered from the catalog, with supported versions, capabilities, instructions and serverInfo |
| capabilities | derived from what you registered, so the declaration cannot drift from the behaviour |
| version negotiation | an unsupported version is -32022 with data.supported, since there is no handshake to renegotiate in |
| the _meta envelope | a request missing protocolVersion or clientCapabilities is -32602, naming the keys |
| removed methods | initialize, ping, logging/setLevel, resources/subscribe are -32601 — and initialize gets a message naming the versions you do speak, because a legacy client has no other diagnostic |
| SEP-2243 headers | Mcp-Method / Mcp-Name / Mcp-Param-* are validated against the body, base64 sentinel values decoded first, integers compared numerically |
| result envelope | resultType, _meta.serverInfo, and cache hints on the six methods allowed to carry them |
| pagination | cursors on every list method |
| tool errors | a handler that raises becomes isError: true content, so the message reaches the model |
| HTTP statuses | each failure carries the status the spec assigns it — 400, 404 or 200 |
pub fn Server::route(self : Self, inbound : @transport.Inbound) -> Route
pub enum Route {
Answer(Json, status~ : Int) // decided outright: discovery, a listing, or a rejection
Drop // a notification, which MUST NOT get a reply
Invoke(...) // call a handler; everything else is already decided
}| # | Rung | Failure | HTTP |
|---|---|---|---|
| 1 | jsonrpc-shape | -32600 | 400 |
| 2 | era-classification | -32022 | 400 |
| 3 | method-registry | -32601 | 404 |
| 4 | envelope | -32602 | 400 |
| 5 | standard-header-validation | -32020 | 400 |
| 6 | param-header-validation | -32020 | 400 |
| 7 | handler | -32602 unknown tool, or isError | 200 |
pub type ToolHandler = async (Args, Ctx) -> ToolResult
pub type ResourceHandler = async (String, Ctx) -> ResourceResult
pub type PromptHandler = async (Args, Ctx) -> PromptResultctx.client_info() // who the client says it is (unverified, for display only)
ctx.has_capability("roots")
ctx.cancelled() // on HTTP, a closed stream IS cancellation
ctx.progress(0.5, total=1.0) // no-op unless the request sent a progressToken
ctx.log("debug", data) // no-op unless the request opted in; a server MUST NOT
// emit notifications/message otherwise
ctx.params // the whole params object, for what sits beside `arguments`pub(open) trait ServerTransport {
async fn serve(Self, async (Inbound, &Responder) -> Unit noraise) -> Unit
fn close(Self) -> Unit
fn describe(Self) -> String
}Define MCP 2026-07-28 servers: a side-effect-free routing core, tools/resources/prompts, and a transport trait. Depends only on marianoguerra/mcp.
Dependencies