relay/server

The description of an MCP server: its tools, resources, prompts, completion handler, tool access policy and identity.

new(tools) builds a Server from bound tools (relay/tool); it panics on a repeated tool name, because tool lists are written in source code. The with_* setters add services and replace earlier values. register_tool and unregister_tool change a description, for example before it is served; a running server changes through relay/http.register_tool or relay/runtime.register_tool. A Server is an immutable value and holds no connection state: serve it with relay/http, relay/stdio or relay/runtime. It holds one random key, made by new, that signs its pagination cursors and input-round state, so a token from one HTTP request stays valid on the next.

import json/blueprint/codec
import relay/content
import relay/resources
import relay/server
import relay/tool

pub fn service() -> server.Server(Nil) {
  let say_input = {
    use text <- codec.field("text", codec.string(), get: fn(text) { text })
    codec.success(text)
  }
  let say =
    tool.define("say", say_input, codec.string())
    |> tool.handle(fn(text) { Ok(text) })
  let readme =
    resources.static("memo://readme", "Readme", fn(_context, uri) {
      Ok([content.text_resource(uri, "Hello")])
    })
  server.new([say])
  |> server.with_resources([readme])
  |> server.with_info("notes", "1.0.0")
}

Types

Why register_tool refused a tool.

pub type RegisterError {
  DuplicateTool(name: String)
}

Constructors

  • DuplicateTool(name: String)

An MCP server description.

pub type Server(context) =
  @internal Server(context)

Values

pub fn describe_register_error(error: RegisterError) -> String

A one-line description of a registration error.

pub fn has_tool(server: Server(context), name: String) -> Bool

Whether a tool with this name is registered.

pub fn new(tools: List(tool.Tool(context))) -> Server(context)

A server with these tools and no other services. Panics when two tools share a name; register tools built from runtime data one by one with register_tool instead.

pub fn register_tool(
  server: Server(context),
  tool: tool.Tool(context),
) -> Result(Server(context), RegisterError)

Adds a tool, or fails when its name is taken.

pub fn tools(server: Server(context)) -> List(tool.Declaration)

The declarations of every registered tool, in registration order and without the access policy applied.

pub fn unregister_tool(
  server: Server(context),
  name: String,
) -> Server(context)

Removes the tool with this name, if there is one.

pub fn with_completion(
  server: Server(context),
  completion: completion.Completion(context),
) -> Server(context)

Sets the completion handler for prompt and resource-template arguments.

pub fn with_info(
  server: Server(context),
  name: String,
  version: String,
) -> Server(context)

Sets the name and version that server/discover reports; the default is relay 0.1.0.

pub fn with_instructions(
  server: Server(context),
  instructions: String,
) -> Server(context)

Sets the instructions server/discover gives clients about using this server.

pub fn with_prompts(
  server: Server(context),
  prompts: List(prompts.Prompt(context)),
) -> Server(context)

Replaces the prompts.

pub fn with_resources(
  server: Server(context),
  resources: List(@internal Resource(context)),
) -> Server(context)

Replaces the static resources and resource templates.

pub fn with_tool_access(
  server: Server(context),
  visible visible: fn(context, tool.Declaration) -> Bool,
  callable callable: fn(context, tool.Declaration) -> Bool,
) -> Server(context)

Decides per request which tools a client sees and may call. visible filters tools/list; a call needs both visible and callable. A hidden, denied and unknown tool all produce the same error, so a client cannot tell them apart. Both default to allowing every tool. Decide argument-dependent permissions inside the handler.

✨ Search Document