///| MCPServer - Main Server Implementation
///|
pub(all) struct ServerRequest {
message : String
/// Queue carrying the request's response string back to the transport
/// writer (`@async.Queue` and `@aqueue.Queue` are the same type).
reply : @aqueue.Queue[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 : @aqueue.Queue[String]
subscription_id : String
cancellation_token : @runtime.CancellationToken
cancellation_registration : @runtime.CancellationRegistration
}
///|
/// A `subscriptions/listen` notification filter (spec
/// basic/patterns/subscriptions#notification-filter). 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 = @validation.parse_json(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
}
}
let id = @types.RequestId::from_json(@validation.parse_json(id_str)) catch {
_ => return notification_json
}
meta.set("io.modelcontextprotocol/subscriptionId", id.to_json())
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; tests may inject a fixed clock.
clock : () -> Int32
registry : ToolRegistry
resource_registry : ResourceRegistry
prompt_registry : PromptRegistry
completion_registry : CompletionRegistry?
tools_enabled : Bool?
resources_enabled : Bool?
prompts_enabled : Bool?
/// Active `subscriptions/listen` streams, keyed by subscription id (the
/// listen request's JSON-RPC id, stringified).
subscriptions : Map[String, ActiveSubscription]
active_requests : Map[String, @runtime.CancellationToken]
mut next_scope_id : Int32
}
///|
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() { (@async.now() / 1000).to_int() },
registry: ToolRegistry::ToolRegistry(),
resource_registry: ResourceRegistry::ResourceRegistry(),
prompt_registry: PromptRegistry::PromptRegistry(),
completion_registry: None,
tools_enabled: None,
resources_enabled: None,
prompts_enabled: None,
subscriptions: {},
active_requests: {},
next_scope_id: 0,
}
}
///|
fn MCPServer::register_tool(
self : MCPServer,
name : String,
description : String,
input_schema : Json,
handler : async (@runtime.RequestContext, Json) -> Result[
@tool.ToolCallOutcome,
@types.MCPError,
],
) -> Unit raise @types.MCPError {
self.registry.register(name, description, input_schema, handler)
}
///|
fn[T : @tool.Tool] MCPServer::register_trait_tool(
self : MCPServer,
tool : T,
) -> Unit raise @types.MCPError {
self.registry.register_trait_tool(tool)
}
///|
fn MCPServer::register_resource(
self : MCPServer,
uri : String,
name : String,
description : String,
mime_type : String,
handler : async (@runtime.RequestContext) -> 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 (@runtime.RequestContext, 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 (@runtime.RequestContext, 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 (@runtime.RequestContext, 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,
principal? : @runtime.Principal,
cancellation_token~ : @runtime.CancellationToken = @runtime.CancellationToken::new(),
progress_reporter? : @runtime.ProgressReporter,
) -> String {
match parse_request_message(request_json) {
Ok(request) =>
self.handle_parsed_request(
request,
principal?,
cancellation_token~,
progress_reporter?,
)
Err(response) => response
}
}
///|
fn parse_request_message(
request_json : String,
) -> Result[@types.JsonRpcRequest, String] {
let parsed = @validation.parse_json(request_json) catch {
_ => return Err(jsonrpc_error_str_no_id(-32700, "Parse error"))
}
match @types.JsonRpcRequest::from_json(parsed) {
Ok(request) => Ok(request)
Err(e) =>
// The message failed structural validation, so its id (if any) is not
// trustworthy — respond without an id member (see
// `jsonrpc_error_str_no_id`).
Err(
Json::object({ "jsonrpc": "2.0", "error": e.to_jsonrpc_error() }).stringify(),
)
}
}
///|
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/index#notifications). 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,
principal? : @runtime.Principal,
cancellation_token~ : @runtime.CancellationToken = @runtime.CancellationToken::new(),
progress_reporter? : @runtime.ProgressReporter,
) -> String {
cancellation_token.throw_if_cancelled()
let meta = match request.params {
Object(params) =>
match params.get("_meta") {
Some(Object(meta)) => meta
_ => {}
}
_ => {}
}
let context = @runtime.RequestContext::new(
request.id,
meta~,
principal?,
cancellation_token~,
progress_reporter?,
)
// Reject unsupported protocol versions early on every request path.
match check_protocol_version(request.id, request.params) {
Some(error_response) => return error_response
None => ()
}
if !self.supports_method(request.method_name) {
return jsonrpc_error_str(
request.id,
-32601,
"Capability is not enabled for " + request.method_name,
)
}
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, context, 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, context, request.params)
"prompts/list" => handle_prompts_list(self, request.id)
"prompts/get" => handle_prompts_get(self, context, request.params)
"completion/complete" => handle_completion(self, context, 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 basic/patterns/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 : @aqueue.Queue[String],
scope~ : String = "",
cancellation_token~ : @runtime.CancellationToken = @runtime.CancellationToken::new(),
) -> Unit {
cancellation_token.throw_if_cancelled()
let filter = self.supported_subscription_filter(
parse_subscription_filter(request.params),
)
let sub_id = request.id.to_json_string()
let key = scoped_request_key(scope, request.id.key())
let cancellation_registration = cancellation_token.on_cancel(fn() {
self.subscriptions.remove(key)
})
// Register before acknowledging so notifications aren't lost.
self.subscriptions.set(key, {
filter,
reply,
subscription_id: sub_id,
cancellation_token,
cancellation_registration,
})
// Send the acknowledgment as the first stream message.
let ack = subscriptions_acknowledged_str(request.id, filter)
reply.put(ack) catch {
_ => {
cancellation_registration.dispose()
self.subscriptions.remove(key)
}
}
}
///|
/// 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,
sub.subscription_id,
)
sub.reply.put(tagged) catch {
_ => {
sub.cancellation_registration.dispose()
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,
scope~ : String = "",
) -> Unit {
let id = @types.RequestId::from_json(@validation.parse_json(subscription_id))
let key = scoped_request_key(scope, id.key())
match self.subscriptions.get(key) {
Some(sub) => {
let result : Map[String, Json] = Default::default()
result.set("resultType", Json::string("complete"))
result.set(
"_meta",
Json::object({
"io.modelcontextprotocol/subscriptionId": @validation.parse_json(
sub.subscription_id,
),
}),
)
let id_json = @validation.parse_json(sub.subscription_id)
let response = Json::object({
"jsonrpc": Json::string("2.0"),
"id": id_json,
"result": Json::object(result),
}).stringify()
sub.reply.put(response) catch {
_ => ()
}
sub.cancellation_registration.dispose()
self.subscriptions.remove(key)
}
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.
fn MCPServer::handle_inbound_notification(
self : MCPServer,
message : String,
scope~ : String = "",
) -> Unit {
let json = @validation.parse_json(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(value) =>
Some(@types.RequestId::from_json(value).key()) catch {
_ => None
}
None => None
}
_ => None
}
match request_id_str {
Some(id) => {
let key = scoped_request_key(scope, id)
match self.active_requests.get(key) {
Some(token) => token.cancel()
None => ()
}
match self.subscriptions.get(key) {
Some(sub) => {
sub.cancellation_registration.dispose()
self.subscriptions.remove(key)
sub.cancellation_token.cancel()
}
None => ()
}
}
None => ()
}
}
_ => ()
}
}
}
///|
async fn MCPServer::handle_server_request(
self : MCPServer,
request : ServerRequest,
group : @async.TaskGroup[Unit],
scope~ : String = "",
principal? : @runtime.Principal,
cancellation_token~ : @runtime.CancellationToken = @runtime.CancellationToken::new(),
) -> Unit {
if is_notification_message(request.message) {
self.handle_inbound_notification(request.message, scope~)
return
}
if cancellation_token.is_cancelled() {
return
}
match parse_request_message(request.message) {
Err(response) => request.reply.put(response) catch { _ => () }
Ok(parsed_request) => {
match check_protocol_version(parsed_request.id, parsed_request.params) {
Some(error_response) => {
request.reply.put(error_response) catch {
_ => ()
}
return
}
None => ()
}
if parsed_request.method_name == "subscriptions/listen" {
self.handle_subscriptions_listen(
parsed_request,
request.reply,
scope~,
cancellation_token~,
)
} else {
self.dispatch_runtime_request(
parsed_request,
request.reply,
group,
scope,
principal,
cancellation_token,
)
}
}
}
}
///|
async fn MCPServer::run_stdio_transport(
self : MCPServer,
transport : @transport.StdioTransport,
group : @async.TaskGroup[Unit],
) -> Unit raise @types.TransportError {
let scope = self.new_request_scope()
defer self.cancel_scope(scope)
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: response_queue, },
group,
scope~,
) catch {
_ => ()
}
}
}
}
///|
async fn MCPServer::run_http_transport(
self : MCPServer,
transport : @transport.HttpTransport,
group : @async.TaskGroup[Unit],
) -> Unit raise @types.TransportError {
transport.set_tool_schema_resolver(fn(name) {
self.registry.tools.get(name).map(entry => entry.definition.input_schema)
})
while true {
match transport.receive_authenticated_request() {
None => {
transport.close()
break
}
Some(request) =>
self.handle_server_request(
{ message: request.message, reply: request.reply, },
group,
scope=self.new_request_scope(),
principal?=request.principal,
cancellation_token=request.cancellation_token,
) 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 raise @types.MCPError {
self.register_trait_tool(tool)
self
}
///|
pub fn MCPServer::tool(
self : MCPServer,
name : String,
description : String,
input_schema : Json,
handler : async (@runtime.RequestContext, Json) -> Result[
@tool.ToolResult,
@types.MCPError,
],
) -> MCPServer raise @types.MCPError {
// Convenience: simple tools return ToolResult and always complete.
// Wrap into ToolCallOutcome for the registry.
let wrapped = async fn(
context : @runtime.RequestContext,
args : Json,
) -> Result[@tool.ToolCallOutcome, @types.MCPError] {
match handler(context, args) {
Ok(r) => Ok(@tool.ToolCallOutcome::Complete(r))
Err(e) => Err(e)
}
}
self.register_tool(name, description, input_schema, wrapped)
self
}
///|
/// Register the complete wire definition without projecting its schema or metadata.
pub fn MCPServer::tool_definition(
self : MCPServer,
definition : @types.ToolDefinition,
handler : async (@runtime.RequestContext, Json) -> Result[
@tool.ToolCallOutcome,
@types.MCPError,
],
) -> MCPServer raise @types.MCPError {
self.registry.register_definition(definition, handler)
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 (@runtime.RequestContext) -> Result[
@resource.ResourceReadResult,
@types.MCPError,
],
) -> MCPServer {
self.register_resource(uri, name, description, mime_type, handler)
self
}
///|
pub fn MCPServer::resource_definition(
self : MCPServer,
definition : @types.ResourceDefinition,
handler : async (@runtime.RequestContext, Json) -> Result[
@resource.ResourceReadOutcome,
@types.MCPError,
],
) -> MCPServer {
self.resource_registry.register_definition(definition, handler)
self
}
///|
pub fn MCPServer::resource_template_definition(
self : MCPServer,
definition : @types.ResourceTemplateDefinition,
) -> MCPServer {
self.resource_registry.register_template_definition(definition)
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 (@runtime.RequestContext, Json) -> Result[
@types.GetPromptResult,
@types.MCPError,
],
) -> MCPServer {
self.register_prompt(name, description, arguments, handler)
self
}
///|
pub fn MCPServer::prompt_definition(
self : MCPServer,
definition : @types.PromptDefinition,
handler : async (@runtime.RequestContext, Json) -> Result[
@prompt.PromptGetOutcome,
@types.MCPError,
],
) -> MCPServer {
self.prompt_registry.register_definition(definition, 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 (@runtime.RequestContext, 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 (@runtime.RequestContext, 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.
/// The default uses the system clock; injection is useful for deterministic tests.
pub fn MCPServer::with_clock(
self : MCPServer,
clock : () -> Int32,
) -> 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~ : Int32 = 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())
}
}