///| MCPServer - Main Server Implementation
///|
pub(all) enum ReplyHandle {
Stdio(@aqueue.Queue[String])
Http(@async.Queue[String])
}
///|
pub(all) struct ServerRequest {
message : String
reply : ReplyHandle
}
///|
async fn ReplyHandle::send(
self : ReplyHandle,
response : String,
) -> Unit raise @types.TransportError {
match self {
Stdio(queue) =>
queue.put(response) catch {
e => raise @types.WriteError(e.to_string())
}
Http(queue) =>
queue.put(response) catch {
e => raise @types.WriteError(e.to_string())
}
}
}
///|
/// One active `subscriptions/listen` stream. The server holds these so it can
/// route change notifications to the right client stream. Keyed by the
/// subscription id (the listen request's JSON-RPC id, stringified).
pub(all) struct ActiveSubscription {
/// Which notification types this subscription opted into.
filter : SubscriptionFilter
/// Where to send notifications for this subscription.
reply : ReplyHandle
}
///|
/// A `subscriptions/listen` notification filter (spec §subscriptions). Each
/// field is optional; omitting a field means "not subscribed to that type".
pub(all) struct SubscriptionFilter {
mut tools_list_changed : Bool
mut prompts_list_changed : Bool
mut resources_list_changed : Bool
mut resource_subscriptions : Array[String]
}
///|
/// An empty filter (subscribe to nothing) — the default when fields are absent.
pub fn SubscriptionFilter::empty() -> SubscriptionFilter {
{
tools_list_changed: false,
prompts_list_changed: false,
resources_list_changed: false,
resource_subscriptions: [],
}
}
///|
/// Does this filter opt into the given notification kind? A
/// `ResourceUpdated(uri)` matches only if the subscription listed that URI
/// in its `resourceSubscriptions` filter.
pub fn SubscriptionFilter::matches(
self : SubscriptionFilter,
kind : @types.SubscriptionNotificationKind,
) -> Bool {
match kind {
ToolsListChanged => self.tools_list_changed
PromptsListChanged => self.prompts_list_changed
ResourcesListChanged => self.resources_list_changed
ResourceUpdated(uri~) => self.resource_subscriptions.contains(uri)
}
}
///|
/// Inject `io.modelcontextprotocol/subscriptionId` into a notification's
/// `_meta`, so the client can demultiplex it to the originating stream.
/// `id_str` is the stringified JSON-RPC id of the listen request. If the
/// notification has no `params` or `_meta`, they are created.
fn tag_notification_with_subscription(
notification_json : String,
id_str : String,
) -> String {
let json = @json.parse(notification_json) catch {
_ => return notification_json
}
if json is Object(obj) {
let params = match obj.get("params") {
Some(Object(p)) => p
_ => {
let p : Map[String, Json] = Map([])
obj.set("params", Json::object(p))
p
}
}
let meta = match params.get("_meta") {
Some(Object(m)) => m
_ => {
let m : Map[String, Json] = Map([])
params.set("_meta", Json::object(m))
m
}
}
meta.set("io.modelcontextprotocol/subscriptionId", Json::string(id_str))
json.stringify()
} else {
notification_json
}
}
///|
pub struct MCPServer {
name : String
version : String
title : String?
description : String?
instructions : String?
auth_config : @transport.AuthConfig?
/// Optional AEAD codec for MRTR `requestState`. When `None`, the server
/// does not issue `input_required` results (MRTR disabled).
request_state_codec : AesGcmStateCodec?
/// Wall-clock source (unix seconds) for MRTR expiry. Defaults to a stub
/// returning 0; production servers inject a real clock so `requestState`
/// expiry is enforced. Test servers inject a fixed value.
clock : () -> Int
registry : ToolRegistry
resource_registry : ResourceRegistry
prompt_registry : PromptRegistry
/// Active `subscriptions/listen` streams, keyed by subscription id (the
/// listen request's JSON-RPC id, stringified).
subscriptions : Map[String, ActiveSubscription]
}
///|
pub fn MCPServer::MCPServer(name : String, version : String) -> MCPServer {
{
name,
version,
title: None,
description: None,
instructions: None,
auth_config: None,
request_state_codec: None,
clock: fn() { 0 },
registry: ToolRegistry::ToolRegistry(),
resource_registry: ResourceRegistry::ResourceRegistry(),
prompt_registry: PromptRegistry::PromptRegistry(),
subscriptions: {},
}
}
///|
fn MCPServer::register_tool(
self : MCPServer,
name : String,
description : String,
input_schema : Json,
handler : async (Json) -> Result[@tool.ToolCallOutcome, @types.MCPError],
) -> Unit {
self.registry.register(name, description, input_schema, handler)
}
///|
fn[T : @tool.Tool] MCPServer::register_trait_tool(
self : MCPServer,
tool : T,
) -> Unit {
self.registry.register_trait_tool(tool)
}
///|
fn MCPServer::register_resource(
self : MCPServer,
uri : String,
name : String,
description : String,
mime_type : String,
handler : async () -> Result[@resource.ResourceReadResult, @types.MCPError],
) -> Unit {
self.resource_registry.register_simple(
uri, name, description, mime_type, handler,
)
}
///|
fn MCPServer::register_resource_mrtr(
self : MCPServer,
uri : String,
name : String,
description : String,
mime_type : String,
handler : async (Json) -> Result[
@resource.ResourceReadOutcome,
@types.MCPError,
],
) -> Unit {
self.resource_registry.register(uri, name, description, mime_type, handler)
}
///|
fn[T : @resource.Resource] MCPServer::register_trait_resource(
self : MCPServer,
resource : T,
) -> Unit {
self.resource_registry.register_trait_resource(resource)
}
///|
fn[T : @resource.ResourceMRTR] MCPServer::register_trait_resource_mrtr(
self : MCPServer,
resource : T,
) -> Unit {
self.resource_registry.register_trait_resource_mrtr(resource)
}
///|
fn MCPServer::register_prompt(
self : MCPServer,
name : String,
description : String,
arguments : Array[@types.PromptArgument],
handler : async (Json) -> Result[@types.GetPromptResult, @types.MCPError],
) -> Unit {
self.prompt_registry.register_simple(name, description, arguments, handler)
}
///|
fn MCPServer::register_prompt_mrtr(
self : MCPServer,
name : String,
description : String,
arguments : Array[@types.PromptArgument],
handler : async (Json) -> Result[@prompt.PromptGetOutcome, @types.MCPError],
) -> Unit {
self.prompt_registry.register(name, description, arguments, handler)
}
///|
fn[T : @prompt.Prompt] MCPServer::register_trait_prompt(
self : MCPServer,
prompt : T,
) -> Unit {
self.prompt_registry.register_trait_prompt(prompt)
}
///|
fn[T : @prompt.PromptMRTR] MCPServer::register_trait_prompt_mrtr(
self : MCPServer,
prompt : T,
) -> Unit {
self.prompt_registry.register_trait_prompt_mrtr(prompt)
}
///|
fn MCPServer::register_resource_template(
self : MCPServer,
uri_template : String,
name : String,
description : String?,
mime_type : String?,
) -> Unit {
self.resource_registry.register_template(
uri_template, name, description, mime_type,
)
}
///|
pub async fn MCPServer::handle_request(
self : MCPServer,
request_json : String,
) -> String {
match parse_request_message(request_json) {
Ok(request) => self.handle_parsed_request(request)
Err(response) => response
}
}
///|
fn parse_request_message(
request_json : String,
) -> Result[@types.JsonRpcRequest, String] {
let parsed = @json.parse(request_json) catch {
_ =>
return Err(
jsonrpc_error_str(@types.RequestId::Int(0), -32700, "Parse error"),
)
}
match @types.JsonRpcRequest::from_json(parsed) {
Ok(request) => Ok(request)
Err(e) =>
Err(
jsonrpc_error_str(
@types.RequestId::Int(0),
e.to_error_code(),
e.message(),
),
)
}
}
///|
fn is_fast_method(method_name : String) -> Bool {
method_name == "server/discover" ||
method_name == "tools/list" ||
method_name == "resources/list" ||
method_name == "resources/templates/list" ||
method_name == "prompts/list"
}
///|
/// A JSON-RPC message with a `method` but no `id` is a notification; the
/// receiver MUST NOT respond to it (spec §basic). Used to route inbound
/// notifications (e.g. stdio `notifications/cancelled`) away from the
/// request path, which would otherwise reject them for the missing `id`.
fn is_notification_message(message : String) -> Bool {
let json = @json.parse(message) catch { _ => return false }
if json is Object(obj) {
obj.get("id") is None && obj.get("method") is Some(String(_))
} else {
false
}
}
///|
async fn MCPServer::handle_parsed_request(
self : MCPServer,
request : @types.JsonRpcRequest,
) -> String {
// Reject unsupported protocol versions early on every request path.
match check_protocol_version(request.id, request.params) {
Some(error_response) => return error_response
None => ()
}
match request.method_name {
"server/discover" => handle_discover(self, request.id)
"tools/list" => handle_tools_list(self, request.id)
"tools/call" => handle_tools_call(self, request.id, request.params)
"resources/list" => handle_resources_list(self, request.id)
"resources/templates/list" =>
handle_resources_templates_list(self, request.id)
"resources/read" => handle_resources_read(self, request.id, request.params)
"prompts/list" => handle_prompts_list(self, request.id)
"prompts/get" => handle_prompts_get(self, request.id, request.params)
// 2026-07-28 server: `initialize` was removed. Name the supported version
// in the error so legacy clients get a clear signal.
"initialize" =>
jsonrpc_error_str(
request.id,
-32601,
"Method not found: initialize — this server speaks MCP 2026-07-28; supportedVersions: [\"2026-07-28\"]",
)
_ =>
jsonrpc_error_str(
request.id,
-32601,
"Method not found: " + request.method_name,
)
}
}
///|
fn internal_error_response(id : @types.RequestId) -> String {
jsonrpc_error_str(id, -32603, "Internal server error")
}
///|
/// Build the `notifications/subscriptions/acknowledged` message: the first
/// message on a listen stream, carrying the subscription id (the listen
/// request's JSON-RPC id) and the agreed-upon filter subset.
fn subscriptions_acknowledged_str(
id : @types.RequestId,
filter : SubscriptionFilter,
) -> String {
let acked : Map[String, Json] = Default::default()
if filter.tools_list_changed {
acked.set("toolsListChanged", Json::boolean(true))
}
if filter.prompts_list_changed {
acked.set("promptsListChanged", Json::boolean(true))
}
if filter.resources_list_changed {
acked.set("resourcesListChanged", Json::boolean(true))
}
if !filter.resource_subscriptions.is_empty() {
acked.set(
"resourceSubscriptions",
Json::array(
filter.resource_subscriptions.map(fn(uri) { Json::string(uri) }),
),
)
}
Json::object({
"jsonrpc": Json::string("2.0"),
"method": Json::string("notifications/subscriptions/acknowledged"),
"params": Json::object({
"_meta": Json::object({
"io.modelcontextprotocol/subscriptionId": request_id_to_json(id),
}),
"notifications": Json::object(acked),
}),
}).stringify()
}
///|
/// Parse the `notifications` filter from a `subscriptions/listen` request's
/// params. Absent fields default to false / empty.
fn parse_subscription_filter(params : Json) -> SubscriptionFilter {
let filter = SubscriptionFilter::empty()
if params is Object(obj) {
match obj.get("notifications") {
Some(Object(n)) => {
match n.get("toolsListChanged") {
Some(True) => filter.tools_list_changed = true
_ => ()
}
match n.get("promptsListChanged") {
Some(True) => filter.prompts_list_changed = true
_ => ()
}
match n.get("resourcesListChanged") {
Some(True) => filter.resources_list_changed = true
_ => ()
}
match n.get("resourceSubscriptions") {
Some(Array(uris)) =>
for uri in uris {
if uri is String(s) {
filter.resource_subscriptions.push(s)
}
}
_ => ()
}
}
_ => ()
}
}
filter
}
///|
/// Handle `subscriptions/listen` (spec §subscriptions). Registers the
/// subscription, sends the acknowledgment (the first message on the stream),
/// and keeps the subscription registered so later `notify_*` calls route
/// matching notifications to this stream. The stream ends when the client
/// cancels or the transport closes.
async fn MCPServer::handle_subscriptions_listen(
self : MCPServer,
request : @types.JsonRpcRequest,
reply : ReplyHandle,
) -> Unit {
let filter = parse_subscription_filter(request.params)
let sub_id = request.id.to_json_string()
// Register before acknowledging so notifications aren't lost.
self.subscriptions.set(sub_id, { filter, reply })
// Send the acknowledgment as the first stream message.
let ack = subscriptions_acknowledged_str(request.id, filter)
reply.send(ack) catch {
_ => self.subscriptions.remove(sub_id)
}
}
///|
/// Push a change notification to every active subscription whose filter
/// matches the given kind. `notification_json` is the full JSON-RPC
/// notification string; `subscription_id` is attached to `_meta` per spec.
/// Subscriptions whose reply has failed are dropped.
async fn MCPServer::notify_subscriptions(
self : MCPServer,
kind : @types.SubscriptionNotificationKind,
notification_json : String,
) -> Unit {
// Snapshot the ids to avoid mutating the map during iteration.
let ids : Array[String] = []
self.subscriptions.each(fn(k, _) { ids.push(k) })
for id in ids {
match self.subscriptions.get(id) {
Some(sub) =>
if SubscriptionFilter::matches(sub.filter, kind) {
let tagged = tag_notification_with_subscription(notification_json, id)
sub.reply.send(tagged) catch {
_ => self.subscriptions.remove(id)
}
}
None => ()
}
}
}
///|
/// Public server-side notification triggers. Each routes to matching
/// `subscriptions/listen` streams.
pub async fn MCPServer::notify_tools_list_changed(self : MCPServer) -> Unit {
let n = @types.tools_list_changed_notification().to_jsonrpc_string()
self.notify_subscriptions(
@types.SubscriptionNotificationKind::ToolsListChanged,
n,
)
}
///|
pub async fn MCPServer::notify_resources_list_changed(self : MCPServer) -> Unit {
let n = @types.resources_list_changed_notification().to_jsonrpc_string()
self.notify_subscriptions(
@types.SubscriptionNotificationKind::ResourcesListChanged,
n,
)
}
///|
/// Announce that a specific resource's content changed. Reaches only the
/// subscriptions that listed `uri` in their `resourceSubscriptions` filter —
/// this is the 2026-07-28 replacement for the legacy `resources/subscribe`
/// per-URI push.
pub async fn MCPServer::notify_resource_updated(
self : MCPServer,
uri : String,
) -> Unit {
let n = @types.resources_updated_notification(uri).to_jsonrpc_string()
self.notify_subscriptions(
@types.SubscriptionNotificationKind::ResourceUpdated(uri~),
n,
)
}
///|
pub async fn MCPServer::notify_prompts_list_changed(self : MCPServer) -> Unit {
let n = @types.prompts_list_changed_notification().to_jsonrpc_string()
self.notify_subscriptions(
@types.SubscriptionNotificationKind::PromptsListChanged,
n,
)
}
///|
/// Gracefully close an active `subscriptions/listen` stream. Sends an empty
/// `complete` result carrying `io.modelcontextprotocol/subscriptionId` and
/// removes the subscription. The caller is responsible for any transport-level
/// stream teardown beyond removing the subscription.
pub async fn MCPServer::close_subscription(
self : MCPServer,
subscription_id : String,
) -> Unit {
match self.subscriptions.get(subscription_id) {
Some(sub) => {
let result : Map[String, Json] = Default::default()
result.set("resultType", Json::string("complete"))
result.set(
"_meta",
Json::object({
"io.modelcontextprotocol/subscriptionId": Json::string(
subscription_id,
),
}),
)
let id_json = @json.parse(subscription_id) catch { _ => Json::null() }
let response = Json::object({
"jsonrpc": Json::string("2.0"),
"id": id_json,
"result": Json::object(result),
}).stringify()
sub.reply.send(response) catch {
_ => ()
}
self.subscriptions.remove(subscription_id)
}
None => ()
}
}
///|
/// Handle inbound notifications (messages with a method but no id).
/// `notifications/cancelled` is treated specially when its `requestId` matches
/// an active subscription: it triggers graceful closure of that subscription.
/// Other notifications are swallowed as required by JSON-RPC 2.0.
async fn MCPServer::handle_inbound_notification(
self : MCPServer,
message : String,
) -> Unit {
let json = @json.parse(message) catch { _ => return }
if json is Object(obj) {
match obj.get("method") {
Some(String("notifications/cancelled")) => {
let request_id_str = match obj.get("params") {
Some(Object(params)) =>
match params.get("requestId") {
Some(Number(n, ..)) =>
Some(@types.RequestId::Int(n.to_int()).to_json_string())
Some(String(s)) => Some(@types.RequestId::Str(s).to_json_string())
_ => None
}
_ => None
}
match request_id_str {
Some(id) =>
if self.subscriptions.contains(id) {
self.close_subscription(id) catch {
_ => ()
}
}
None => ()
}
}
_ => ()
}
}
}
///|
async fn MCPServer::handle_server_request(
self : MCPServer,
request : ServerRequest,
group : @async.TaskGroup[Unit],
) -> Unit {
// A message without an `id` is a notification (e.g. stdio
// `notifications/cancelled`): the receiver MUST NOT respond to the
// notification itself. Acting on cancellation (stopping in-flight work)
// is a follow-up; here we only close matching subscriptions.
if is_notification_message(request.message) {
self.handle_inbound_notification(request.message) catch {
_ => ()
}
return
}
match parse_request_message(request.message) {
Err(response) => request.reply.send(response) catch { _ => () }
Ok(parsed_request) => {
// Reject unsupported protocol versions before routing. This covers the
// stdio/HTTP common path, including subscriptions/listen.
match check_protocol_version(parsed_request.id, parsed_request.params) {
Some(error_response) => {
request.reply.send(error_response) catch {
_ => ()
}
return
}
None => ()
}
// subscriptions/listen is a long-lived request: it registers a
// subscription, sends an acknowledgment, and stays open for future
// notifications. Handle it specially (not via handle_parsed_request,
// which returns a single response string).
if parsed_request.method_name == "subscriptions/listen" {
group.spawn_bg(() => {
self.handle_subscriptions_listen(parsed_request, request.reply) catch {
_ => ()
}
})
} else if is_fast_method(parsed_request.method_name) {
let response = self.handle_parsed_request(parsed_request) catch {
_ => internal_error_response(parsed_request.id)
}
request.reply.send(response) catch {
_ => ()
}
} else {
group.spawn_bg(() => {
let response = self.handle_parsed_request(parsed_request) catch {
_ => internal_error_response(parsed_request.id)
}
request.reply.send(response) catch {
_ => ()
}
})
}
}
}
}
///|
async fn MCPServer::run_stdio_transport(
self : MCPServer,
transport : @transport.StdioTransport,
group : @async.TaskGroup[Unit],
) -> Unit raise @types.TransportError {
let response_queue : @aqueue.Queue[String] = @aqueue.Queue(
kind=@aqueue.Kind::Unbounded,
)
group.spawn_bg(() => {
while true {
let response = response_queue.get() catch { _ => break }
transport.send(response) catch {
_ => ()
}
}
})
while true {
match transport.receive() {
None => {
response_queue.close()
transport.close()
break
}
Some(message) =>
self.handle_server_request(
{ message, reply: ReplyHandle::Stdio(response_queue) },
group,
) catch {
_ => ()
}
}
}
}
///|
async fn MCPServer::run_http_transport(
self : MCPServer,
transport : @transport.HttpTransport,
group : @async.TaskGroup[Unit],
) -> Unit raise @types.TransportError {
while true {
match transport.receive_request() {
None => {
transport.close()
break
}
Some((message, reply_queue)) =>
self.handle_server_request(
{ message, reply: ReplyHandle::Http(reply_queue) },
group,
) catch {
_ => ()
}
}
}
}
///|
async fn MCPServer::run_with_transport(
self : MCPServer,
transport : @transport.AnyTransport,
group : @async.TaskGroup[Unit],
) -> Unit raise @types.TransportError {
match transport {
Stdio(stdio) => self.run_stdio_transport(stdio, group)
Http(http) => self.run_http_transport(http, group)
_ =>
raise @types.InvalidState(
"MCPServer::run requires a server transport (stdio or http)",
)
}
}
///|
pub fn[T : @tool.Tool] MCPServer::with_tool(
self : MCPServer,
tool : T,
) -> MCPServer {
self.register_trait_tool(tool)
self
}
///|
pub fn MCPServer::tool(
self : MCPServer,
name : String,
description : String,
input_schema : Json,
handler : async (Json) -> Result[@tool.ToolResult, @types.MCPError],
) -> MCPServer {
// Convenience: simple tools return ToolResult and always complete.
// Wrap into ToolCallOutcome for the registry.
let wrapped = async fn(
args : Json,
) -> Result[@tool.ToolCallOutcome, @types.MCPError] {
match handler(args) {
Ok(r) => Ok(@tool.ToolCallOutcome::Complete(r))
Err(e) => Err(e)
}
}
self.register_tool(name, description, input_schema, wrapped)
self
}
///|
pub fn[T : @resource.Resource] MCPServer::with_resource(
self : MCPServer,
resource : T,
) -> MCPServer {
self.register_trait_resource(resource)
self
}
///|
pub fn MCPServer::resource(
self : MCPServer,
uri : String,
name : String,
description : String,
mime_type : String,
handler : async () -> Result[@resource.ResourceReadResult, @types.MCPError],
) -> MCPServer {
self.register_resource(uri, name, description, mime_type, handler)
self
}
///|
pub fn[T : @prompt.Prompt] MCPServer::with_prompt(
self : MCPServer,
prompt : T,
) -> MCPServer {
self.register_trait_prompt(prompt)
self
}
///|
pub fn MCPServer::prompt(
self : MCPServer,
name : String,
description : String,
arguments : Array[@types.PromptArgument],
handler : async (Json) -> Result[@types.GetPromptResult, @types.MCPError],
) -> MCPServer {
self.register_prompt(name, description, arguments, handler)
self
}
///|
/// Register a resource template for `resources/templates/list`. Templates are
/// URI patterns the server can read; this call only advertises the template.
pub fn MCPServer::resource_template(
self : MCPServer,
uri_template~ : String,
name~ : String,
description? : String = "",
mime_type? : String = "",
) -> MCPServer {
let description = if description == "" { None } else { Some(description) }
let mime_type = if mime_type == "" { None } else { Some(mime_type) }
self.register_resource_template(uri_template, name, description, mime_type)
self
}
///|
/// MRTR-aware resource registration. The handler receives the full read params
/// and may return `InputRequired` to ask the client for input before
/// completing.
pub fn MCPServer::resource_mrtr(
self : MCPServer,
uri : String,
name : String,
description : String,
mime_type : String,
handler : async (Json) -> Result[
@resource.ResourceReadOutcome,
@types.MCPError,
],
) -> MCPServer {
self.register_resource_mrtr(uri, name, description, mime_type, handler)
self
}
///|
pub fn[T : @resource.ResourceMRTR] MCPServer::with_resource_mrtr(
self : MCPServer,
resource : T,
) -> MCPServer {
self.register_trait_resource_mrtr(resource)
self
}
///|
/// MRTR-aware prompt registration. The handler receives the full get params
/// and may return `InputRequired` to ask the client for input before
/// completing.
pub fn MCPServer::prompt_mrtr(
self : MCPServer,
name : String,
description : String,
arguments : Array[@types.PromptArgument],
handler : async (Json) -> Result[@prompt.PromptGetOutcome, @types.MCPError],
) -> MCPServer {
self.register_prompt_mrtr(name, description, arguments, handler)
self
}
///|
pub fn[T : @prompt.PromptMRTR] MCPServer::with_prompt_mrtr(
self : MCPServer,
prompt : T,
) -> MCPServer {
self.register_trait_prompt_mrtr(prompt)
self
}
///|
pub fn MCPServer::with_title(self : MCPServer, title : String) -> MCPServer {
{ ..self, title: Some(title) }
}
///|
pub fn MCPServer::with_description(
self : MCPServer,
description : String,
) -> MCPServer {
{ ..self, description: Some(description) }
}
///|
pub fn MCPServer::with_instructions(
self : MCPServer,
instructions : String,
) -> MCPServer {
{ ..self, instructions: Some(instructions) }
}
///|
pub fn MCPServer::with_auth(
self : MCPServer,
auth : @transport.AuthConfig,
) -> MCPServer {
{ ..self, auth_config: Some(auth) }
}
///|
/// Enable MRTR (Multi Round-Trip Requests) by supplying a `requestState`
/// codec. When set, handlers may return `input_required` results and verify
/// `requestState` on retry. Without a codec, MRTR is disabled.
pub fn MCPServer::with_request_state_codec(
self : MCPServer,
codec : AesGcmStateCodec,
) -> MCPServer {
{ ..self, request_state_codec: Some(codec) }
}
///|
/// Inject a wall-clock source (unix seconds) for MRTR `requestState` expiry.
/// Production servers should pass a real clock; the default returns 0, which
/// disables expiry enforcement (blobs never expire).
pub fn MCPServer::with_clock(self : MCPServer, clock : () -> Int) -> MCPServer {
{ ..self, clock, }
}
///|
pub async fn MCPServer::run_stdio(
self : MCPServer,
) -> Unit raise @types.TransportError {
let transport = @transport.AnyTransport::Stdio(
@transport.StdioTransport::StdioTransport(),
)
@async.with_task_group(group => self.run_with_transport(transport, group)) catch {
e => raise @types.ReadError("TaskGroup error: " + e.to_string())
}
}
///|
pub async fn MCPServer::run_http(
self : MCPServer,
port? : Int = 4240,
path? : String = "/mcp",
) -> Unit raise @types.TransportError {
@async.with_task_group(group => {
let http = @transport.HttpTransport::HttpTransport(
port~,
endpoint_path=path,
)
let http = match self.auth_config {
Some(auth) => http.with_auth(auth)
None => http
}
group.spawn_bg(async fn() { http.start() })
let transport = @transport.AnyTransport::Http(http)
self.run_with_transport(transport, group)
}) catch {
e => raise @types.ReadError("TaskGroup error: " + e.to_string())
}
}
///|
pub fn mcp_server(name~ : String, version~ : String) -> MCPServer {
MCPServer::MCPServer(name, version)
}