///|
/// A server-initiated request carried inside an MRTR result. It deliberately
/// omits JSON-RPC framing: keys in `inputRequests` provide correlation.
pub struct InputRequest {
method_name : String
params : Json
} derive(Debug, Eq)
///|
pub fn InputRequest::InputRequest(
method_name~ : String,
params? : Json = Json::object({}),
) -> InputRequest raise McpError {
guard params is Object(_) else {
raise InvalidParams("input request params must be an object")
}
validate_core_input_request(method_name, params)
{ method_name, params }
}
///|
pub(all) enum SamplingContentBlock {
Text(text~ : String)
Image(data~ : String, mime_type~ : String)
Audio(data~ : String, mime_type~ : String)
ToolUse(id~ : String, name~ : String, input~ : Map[String, Json])
ToolResult(tool_use_id~ : String, content~ : Array[Content])
} derive(Debug, Eq)
///|
pub impl @json.FromJson for SamplingContentBlock with fn from_json(
value : Json,
path : @json.JsonPath,
) -> SamplingContentBlock raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
match fields.get("type") {
Some(String("text")) =>
match fields.get("text") {
Some(String(text)) => Text(text~)
Some(_) =>
raise JsonDecodeError((path.add_key("text"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("text"), "text is required"))
}
Some(String("image")) => {
let data = match fields.get("data") {
Some(String(d)) => d
Some(_) =>
raise JsonDecodeError((path.add_key("data"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("data"), "data is required"))
}
let mime_type = match fields.get("mimeType") {
Some(String(m)) => m
Some(_) =>
raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
None =>
raise JsonDecodeError(
(path.add_key("mimeType"), "mimeType is required"),
)
}
Image(data~, mime_type~)
}
Some(String("audio")) => {
let data = match fields.get("data") {
Some(String(d)) => d
Some(_) =>
raise JsonDecodeError((path.add_key("data"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("data"), "data is required"))
}
let mime_type = match fields.get("mimeType") {
Some(String(m)) => m
Some(_) =>
raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
None =>
raise JsonDecodeError(
(path.add_key("mimeType"), "mimeType is required"),
)
}
Audio(data~, mime_type~)
}
Some(String("tool_use")) => {
let id = match fields.get("id") {
Some(String(i)) => i
Some(_) =>
raise JsonDecodeError((path.add_key("id"), "expected string"))
None => raise JsonDecodeError((path.add_key("id"), "id is required"))
}
let name = match fields.get("name") {
Some(String(n)) => n
Some(_) =>
raise JsonDecodeError((path.add_key("name"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("name"), "name is required"))
}
let input = match fields.get("input") {
Some(Object(inp)) => inp
Some(_) =>
raise JsonDecodeError((path.add_key("input"), "expected object"))
None =>
raise JsonDecodeError((path.add_key("input"), "input is required"))
}
ToolUse(id~, name~, input~)
}
Some(String("tool_result")) => {
let tool_use_id = match fields.get("toolUseId") {
Some(String(id)) => id
Some(_) =>
raise JsonDecodeError((path.add_key("toolUseId"), "expected string"))
None =>
raise JsonDecodeError(
(path.add_key("toolUseId"), "toolUseId is required"),
)
}
let contents : Array[Content] = match fields.get("content") {
Some(Array(arr)) => {
let result : Array[Content] = []
for i, it in arr {
result.push(
@json.from_json(it, path=path.add_key("content").add_index(i)),
)
}
result
}
Some(_) =>
raise JsonDecodeError((path.add_key("content"), "expected array"))
None =>
raise JsonDecodeError(
(path.add_key("content"), "content is required"),
)
}
ToolResult(tool_use_id~, content=contents)
}
Some(String(other)) =>
raise JsonDecodeError(
(path.add_key("type"), "unknown sampling content type: \{other}"),
)
Some(_) => raise JsonDecodeError((path.add_key("type"), "expected string"))
None => raise JsonDecodeError((path.add_key("type"), "type is required"))
}
}
///|
pub fn SamplingContentBlock::from_json(
value : Json,
path? : @json.JsonPath,
) -> SamplingContentBlock raise McpError {
decode_json(value, path?)
}
///|
pub impl ToJson for SamplingContentBlock with fn to_json(
self : SamplingContentBlock,
) -> Json {
match self {
Text(text~) =>
Json::object({ "type": Json::string("text"), "text": Json::string(text) })
Image(data~, mime_type~) =>
Json::object({
"type": Json::string("image"),
"data": Json::string(data),
"mimeType": Json::string(mime_type),
})
Audio(data~, mime_type~) =>
Json::object({
"type": Json::string("audio"),
"data": Json::string(data),
"mimeType": Json::string(mime_type),
})
ToolUse(id~, name~, input~) =>
Json::object({
"type": Json::string("tool_use"),
"id": Json::string(id),
"name": Json::string(name),
"input": Json::object(input),
})
ToolResult(tool_use_id~, content~) =>
Json::object({
"type": Json::string("tool_result"),
"toolUseId": Json::string(tool_use_id),
"content": Json::array(content.map(fn(c) { ToJson::to_json(c) })),
})
}
}
///|
pub(all) enum SamplingContent {
Single(SamplingContentBlock)
Multiple(Array[SamplingContentBlock])
} derive(Debug, Eq)
///|
pub impl @json.FromJson for SamplingContent with fn from_json(
value : Json,
path : @json.JsonPath,
) -> SamplingContent raise {
match value {
Array(values) if !values.is_empty() => {
let blocks : Array[SamplingContentBlock] = []
for i, item in values {
blocks.push(@json.from_json(item, path=path.add_index(i)))
}
Multiple(blocks)
}
Object(_) => Single(@json.from_json(value, path~))
_ => raise JsonDecodeError((path, "expected object or array"))
}
}
///|
pub fn SamplingContent::from_json(
value : Json,
path? : @json.JsonPath,
) -> SamplingContent raise McpError {
decode_json(value, path?)
}
///|
pub impl ToJson for SamplingContent with fn to_json(self : SamplingContent) -> Json {
match self {
Single(block) => ToJson::to_json(block)
Multiple(blocks) => Json::array(blocks.map(fn(b) { ToJson::to_json(b) }))
}
}
///|
pub struct SamplingMessage {
role : Role
content : SamplingContent
} derive(Debug, Eq)
///|
pub impl @json.FromJson for SamplingMessage with fn from_json(
value : Json,
path : @json.JsonPath,
) -> SamplingMessage raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
guard fields.get("role") is Some(role_val) else {
raise JsonDecodeError((path.add_key("role"), "role is required"))
}
let role : Role = @json.from_json(role_val, path=path.add_key("role"))
guard fields.get("content") is Some(content_val) else {
raise JsonDecodeError((path.add_key("content"), "content is required"))
}
let content : SamplingContent = @json.from_json(
content_val,
path=path.add_key("content"),
)
{ role, content }
}
///|
pub fn SamplingMessage::from_json(
value : Json,
path? : @json.JsonPath,
) -> SamplingMessage raise McpError {
decode_json(value, path?)
}
///|
pub impl ToJson for SamplingMessage with fn to_json(self : SamplingMessage) -> Json {
Json::object({
"role": ToJson::to_json(self.role),
"content": ToJson::to_json(self.content),
})
}
///|
pub struct SamplingCreateMessageParams {
messages : Array[SamplingMessage]
max_tokens : Double
} derive(Debug, Eq)
///|
pub impl @json.FromJson for SamplingCreateMessageParams with fn from_json(
value : Json,
path : @json.JsonPath,
) -> SamplingCreateMessageParams raise {
match value {
Object(
{
"messages": Array(messages_arr),
"maxTokens": Number(max_tokens, ..),
..
}
) => {
let messages : Array[SamplingMessage] = []
for index, message_json in messages_arr {
messages.push(
@json.from_json(
message_json,
path=path.add_key("messages").add_index(index),
),
)
}
{ messages, max_tokens }
}
Object({ "messages": Array(_), .. }) =>
raise @json.JsonDecodeError(
(
path.add_key("maxTokens"),
"sampling/createMessage params.maxTokens is required",
),
)
_ =>
raise @json.JsonDecodeError(
(
path.add_key("messages"),
"sampling/createMessage params.messages must be an array",
),
)
}
}
///|
pub fn SamplingCreateMessageParams::from_json(
value : Json,
path? : @json.JsonPath,
) -> SamplingCreateMessageParams raise McpError {
decode_json(value, path?)
}
///|
pub struct SamplingCreateMessageResult {
model : String
role : Role
content : SamplingContent
} derive(Debug, Eq)
///|
pub impl @json.FromJson for SamplingCreateMessageResult with fn from_json(
value : Json,
path : @json.JsonPath,
) -> SamplingCreateMessageResult raise {
match value {
Object(
{
"model": String(model),
"role": String("user"),
"content": content_val,
..
}
) => {
let content : SamplingContent = @json.from_json(
content_val,
path=path.add_key("content"),
)
{ model, role: Role::User, content }
}
Object(
{
"model": String(model),
"role": String("assistant"),
"content": content_val,
..
}
) => {
let content : SamplingContent = @json.from_json(
content_val,
path=path.add_key("content"),
)
{ model, role: Role::Assistant, content }
}
Object({ "model": String(_), "role": String("user" | "assistant"), .. }) =>
raise @json.JsonDecodeError(
(path.add_key("content"), "sampling response.content is required"),
)
Object({ "model": String(_), .. }) =>
raise @json.JsonDecodeError(
(path.add_key("role"), "sampling response.role is invalid"),
)
_ =>
raise @json.JsonDecodeError(
(path.add_key("model"), "sampling response.model is required"),
)
}
}
///|
pub fn SamplingCreateMessageResult::from_json(
value : Json,
path? : @json.JsonPath,
) -> SamplingCreateMessageResult raise McpError {
decode_json(value, path?)
}
///|
pub(all) enum ElicitationPrimitiveType {
String
Number
Integer
Boolean
Enum(Array[String])
} derive(Debug, Eq)
///|
pub struct ElicitationPrimitiveSchema {
schema_type : ElicitationPrimitiveType
raw : Json
} derive(Debug, Eq)
///|
pub impl @json.FromJson for ElicitationPrimitiveSchema with fn from_json(
value : Json,
path : @json.JsonPath,
) -> ElicitationPrimitiveSchema raise {
match value {
Object({ "type": String("string"), .. }) =>
{ schema_type: String, raw: value }
Object({ "type": String("number"), .. }) =>
{ schema_type: Number, raw: value }
Object({ "type": String("integer"), .. }) =>
{ schema_type: Integer, raw: value }
Object({ "type": String("boolean"), .. }) =>
{ schema_type: Boolean, raw: value }
Object(
{
"type": String("array"),
"items": Object({ "type": String("string"), "enum": Array(arr), .. }),
..
}
) => {
let strings : Array[String] = []
for it in arr {
if it is String(s) {
strings.push(s)
}
}
{ schema_type: Enum(strings), raw: value }
}
Object(
{
"type": String("array"),
"items": Object({ "type": String("string"), "anyOf": Array(arr), .. }),
..
}
) => {
let strings : Array[String] = []
for it in arr {
if it is String(s) {
strings.push(s)
}
}
{ schema_type: Enum(strings), raw: value }
}
Object(
{
"type": String("array"),
"items": Object({ "type": String("string"), .. }),
..
}
) =>
raise @json.JsonDecodeError(
(
path.add_key("items"),
"elicitation array schema items require enum options",
),
)
Object({ "type": String("array"), "items": Object(_), .. }) =>
raise @json.JsonDecodeError(
(
path.add_key("items"),
"elicitation array schema items must be strings",
),
)
Object({ "type": String("array"), "items": _, .. }) =>
raise @json.JsonDecodeError(
(path.add_key("items"), "elicitation enum items must be an object"),
)
Object({ "type": String("array"), .. }) =>
raise @json.JsonDecodeError(
(path.add_key("items"), "elicitation array schema.items is required"),
)
Object(_) =>
raise @json.JsonDecodeError(
(path, "elicitation properties must use primitive schemas"),
)
_ =>
raise @json.JsonDecodeError(
(path, "elicitation primitive schema must be an object"),
)
}
}
///|
pub fn ElicitationPrimitiveSchema::from_json(
value : Json,
path? : @json.JsonPath,
) -> ElicitationPrimitiveSchema raise McpError {
decode_json(value, path?)
}
///|
pub(all) enum ElicitationMode {
Url(String)
Form(
requested_schema~ : Json,
properties~ : Map[String, ElicitationPrimitiveSchema],
compiled_schema~ : @schema.Schema
)
} derive(Debug)
///|
pub struct ElicitationCreateParams {
message : String
mode : ElicitationMode
} derive(Debug)
///|
pub impl @json.FromJson for ElicitationCreateParams with fn from_json(
value : Json,
path : @json.JsonPath,
) -> ElicitationCreateParams raise {
guard value is Object(fields) else {
raise @json.JsonDecodeError(
(path, "elicitation/create params must be an object"),
)
}
guard fields is { "message": String(message), .. } else {
raise @json.JsonDecodeError(
(path.add_key("message"), "elicitation/create params.message is required"),
)
}
let parse_form = fn() -> ElicitationMode raise @json.JsonDecodeError {
guard fields
is {
"requestedSchema": Object(
{ "type": String("object"), "properties": Object(properties_map), .. } as schema
),
..
} else {
guard fields is { "requestedSchema": Object(_), .. } else {
raise @json.JsonDecodeError(
(
path.add_key("requestedSchema"),
"elicitation/create form params.requestedSchema is required",
),
)
}
raise @json.JsonDecodeError(
(
path.add_key("requestedSchema"),
"elicitation/create requestedSchema requires object properties",
),
)
}
let properties : Map[String, ElicitationPrimitiveSchema] = Map([])
for key, property_schema in properties_map {
properties[key] = @json.from_json(
property_schema,
path=path.add_key("requestedSchema").add_key("properties").add_key(key),
)
}
let compiled_schema = compile_mrtr_schema(Json::object(schema)) catch {
_ =>
raise @json.JsonDecodeError(
(path.add_key("requestedSchema"), "invalid JSON schema"),
)
}
ElicitationMode::Form(
requested_schema=Json::object(schema),
properties~,
compiled_schema~,
)
}
let mode = match fields {
{ "mode": String("url"), "url": String(url), .. } =>
ElicitationMode::Url(url)
{ "mode": String("url"), .. } =>
raise @json.JsonDecodeError(
(path.add_key("url"), "elicitation/create params.url is required"),
)
{ "mode": String("form"), .. } => parse_form()
{ "mode": String(_), .. } =>
raise @json.JsonDecodeError(
(path.add_key("mode"), "elicitation/create params.mode is invalid"),
)
{ "mode": _, .. } =>
raise @json.JsonDecodeError(
(path.add_key("mode"), "elicitation/create params.mode is invalid"),
)
_ => parse_form()
}
{ message, mode }
}
///|
pub fn ElicitationCreateParams::from_json(
value : Json,
path? : @json.JsonPath,
) -> ElicitationCreateParams raise McpError {
decode_json(value, path?)
}
///|
pub(all) enum ElicitationAction {
Accept
Decline
Cancel
} derive(Debug, Eq)
///|
pub struct ElicitationCreateResult {
action : ElicitationAction
content : Json?
} derive(Debug, Eq)
///|
pub impl @json.FromJson for ElicitationCreateResult with fn from_json(
value : Json,
path : @json.JsonPath,
) -> ElicitationCreateResult raise {
match value {
Object({ "action": String("accept"), .. } as fields) =>
{ action: Accept, content: fields.get("content") }
Object({ "action": String("decline"), .. } as fields) =>
{ action: Decline, content: fields.get("content") }
Object({ "action": String("cancel"), .. } as fields) =>
{ action: Cancel, content: fields.get("content") }
_ =>
raise @json.JsonDecodeError(
(path.add_key("action"), "elicitation response.action is invalid"),
)
}
}
///|
pub fn ElicitationCreateResult::from_json(
value : Json,
path? : @json.JsonPath,
) -> ElicitationCreateResult raise McpError {
decode_json(value, path?)
}
///|
pub fn ElicitationCreateResult::validate_against_request(
self : ElicitationCreateResult,
request_params : Json,
) -> Unit raise McpError {
if self.action == Accept {
guard request_params is Object(request) else {
raise InvalidParams("elicitation request must be an object")
}
if !(request is { "mode": String("url"), .. }) {
guard self.content is Some(content_val) else {
raise InvalidParams(
"accepted form elicitation response.content is required",
)
}
guard request is { "requestedSchema": request_schema, .. } else {
raise InvalidParams("elicitation request.requestedSchema is required")
}
let validator = compile_mrtr_schema(request_schema)
validator.validate(content_val) catch {
ValidationIssues(issues) => {
let error = issues.to_json().stringify()
raise InvalidParams(
"elicitation response.content does not match requestedSchema: \{error}",
)
}
}
}
}
}
///|
pub impl ToJson for ElicitationCreateResult with fn to_json(
self : ElicitationCreateResult,
) -> Json {
let fields : Map[String, Json] = {
"action": match self.action {
Accept => Json::string("accept")
Decline => Json::string("decline")
Cancel => Json::string("cancel")
},
}
fields.set_opt("content", self.content)
Json::object(fields)
}
///|
pub struct Root {
uri : String
name : String?
} derive(Debug, Eq)
///|
pub impl @json.FromJson for Root with fn from_json(
value : Json,
path : @json.JsonPath,
) -> Root raise {
match value {
Object({ "uri": String(uri), "name": String(name), .. }) =>
{ uri, name: Some(name) }
Object({ "uri": String(uri), "name": Null, .. }) => { uri, name: None }
Object({ "uri": String(_), "name": _, .. }) =>
raise @json.JsonDecodeError(
(path.add_key("name"), "roots response root.name must be a string"),
)
Object({ "uri": String(uri), .. }) => { uri, name: None }
Object(_) =>
raise @json.JsonDecodeError(
(path.add_key("uri"), "roots response root.uri is required"),
)
_ =>
raise @json.JsonDecodeError(
(path, "roots response root must be an object"),
)
}
}
///|
pub fn Root::from_json(
value : Json,
path? : @json.JsonPath,
) -> Root raise McpError {
decode_json(value, path?)
}
///|
pub impl ToJson for Root with fn to_json(self : Root) -> Json {
let fields : Map[String, Json] = { "uri": Json::string(self.uri) }
fields.set_opt("name", self.name.map(Json::string))
Json::object(fields)
}
///|
pub struct RootsListResult {
roots : Array[Root]
} derive(Debug, Eq)
///|
pub impl @json.FromJson for RootsListResult with fn from_json(
value : Json,
path : @json.JsonPath,
) -> RootsListResult raise {
match value {
Object({ "roots": Array(roots_arr), .. }) => {
let roots : Array[Root] = []
for index, root_json in roots_arr {
roots.push(
@json.from_json(
root_json,
path=path.add_key("roots").add_index(index),
),
)
}
{ roots, }
}
_ =>
raise @json.JsonDecodeError(
(path.add_key("roots"), "roots response.roots is required"),
)
}
}
///|
pub fn RootsListResult::from_json(
value : Json,
path? : @json.JsonPath,
) -> RootsListResult raise McpError {
decode_json(value, path?)
}
///|
pub impl ToJson for RootsListResult with fn to_json(self : RootsListResult) -> Json {
Json::object({
"roots": Json::array(self.roots.map(fn(r) { ToJson::to_json(r) })),
})
}
///|
fn validate_core_input_request(
method_name : String,
params : Json,
) -> Unit raise McpError {
match method_name {
"elicitation/create" => ignore(ElicitationCreateParams::from_json(params))
"sampling/createMessage" =>
ignore(SamplingCreateMessageParams::from_json(params))
"roots/list" => ()
_ => ()
}
}
///|
fn validate_core_input_response(
method_name : String,
request_params : Json,
result : Json,
) -> Unit raise McpError {
match method_name {
"elicitation/create" => {
let elicitation = ElicitationCreateResult::from_json(result)
elicitation.validate_against_request(request_params)
}
"sampling/createMessage" =>
ignore(SamplingCreateMessageResult::from_json(result))
"roots/list" => ignore(RootsListResult::from_json(result))
_ => ()
}
}
///|
pub impl ToJson for InputRequest with fn to_json(self : InputRequest) -> Json {
Json::object({
"method": Json::string(self.method_name),
"params": self.params,
})
}
///|
pub impl @json.FromJson for InputRequest with fn from_json(
value : Json,
path : @json.JsonPath,
) -> InputRequest raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
let method_name = match fields.get("method") {
Some(String(s)) => s
Some(_) =>
raise JsonDecodeError((path.add_key("method"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("method"), "method is required"))
}
let params = fields.get("params").unwrap_or(Json::object({}))
InputRequest(method_name~, params~) catch {
error => raise JsonDecodeError((path, error.message()))
}
}
///|
pub fn InputRequest::from_json(
value : Json,
path? : @json.JsonPath,
) -> InputRequest raise McpError {
decode_json(value, path?)
}
///|
/// A raw client result for a server-initiated input request.
pub struct InputResponse {
result : Json
} derive(Debug, Eq)
///|
pub fn InputResponse::InputResponse(result : Json) -> InputResponse {
{ result: result.copy() }
}
///|
pub impl @json.FromJson for InputResponse with fn from_json(
value : Json,
_path : @json.JsonPath,
) -> InputResponse {
InputResponse(value)
}
///|
pub fn InputResponse::from_json(value : Json) -> InputResponse {
InputResponse(value)
}
///|
pub impl ToJson for InputResponse with fn to_json(self : InputResponse) -> Json {
self.result
}
///|
pub fn InputResponse::map_from_json(
value : Json,
) -> Map[String, InputResponse] raise McpError {
guard value is Object(fields) else {
raise InvalidParams("inputResponses must be an object")
}
let responses : Map[String, InputResponse] = Map([])
for key, result in fields {
responses[key] = InputResponse::from_json(result)
}
responses
}
///|
pub struct InputRequiredResult {
input_requests : Map[String, InputRequest]?
request_state : String?
meta : ResultMeta?
} derive(Debug, Eq)
///|
pub fn InputRequiredResult::InputRequiredResult(
input_requests? : Map[String, InputRequest],
request_state? : String,
meta? : ResultMeta,
) -> InputRequiredResult raise McpError {
let has_requests = match input_requests {
Some(requests) => !requests.is_empty()
None => false
}
if !has_requests && request_state is None {
raise InvalidParams(
"input_required result needs non-empty inputRequests or requestState",
)
}
{
input_requests: input_requests.map(fn(
requests,
) -> Map[String, InputRequest] raise McpError {
let copied : Map[String, InputRequest] = Map([])
for key, request in requests {
copied[key] = InputRequest(
method_name=request.method_name,
params=request.params.copy(),
)
}
copied
}),
request_state,
meta: meta.map(fn(m) { m.copy() }),
}
}
///|
/// Build retry options while preserving the server's opaque request state.
/// Every requested input must have exactly one response; callers never need to
/// inspect or copy `requestState` themselves.
pub fn InputRequiredResult::retry_options(
self : InputRequiredResult,
responses : Map[String, InputResponse],
base? : RequestOptions = RequestOptions(),
) -> RequestOptions raise McpError {
match self.input_requests {
Some(requests) => {
for key, _ in requests {
if !responses.contains(key) {
raise InvalidParams("missing input response: \{key}")
}
}
for key, _ in responses {
if !requests.contains(key) {
raise InvalidParams("unexpected input response: \{key}")
}
}
for key, request in requests {
validate_core_input_response(
request.method_name,
request.params,
responses[key].result,
)
}
}
None if !responses.is_empty() =>
raise InvalidParams("input_required result did not request responses")
None => ()
}
RequestOptions(
progress_token?=base.progress_token,
log_level?=base.log_level,
input_responses?=if responses.is_empty() { None } else { Some(responses) },
request_state?=self.request_state,
meta=base.meta,
)
}
///|
/// Compile an MRTR request schema through the independent JSON Schema package.
fn compile_mrtr_schema(value : Json) -> @schema.Schema raise McpError {
@schema.Schema::from_json(value) catch {
_ => raise InvalidParams("MRTR requestedSchema is not a valid JSON Schema")
}
}
///|
pub impl ToJson for InputRequiredResult with fn to_json(
self : InputRequiredResult,
) -> Json {
let fields : Map[String, Json] = {
"resultType": ToJson::to_json(ResultType::InputRequired),
}
if self.input_requests is Some(requests) {
let values : Map[String, Json] = Map([])
for key, request in requests {
values[key] = ToJson::to_json(request)
}
fields["inputRequests"] = Json::object(values)
}
fields.set_opt("requestState", self.request_state.map(Json::string))
if self.meta is Some(value) {
fields["_meta"] = ToJson::to_json(value)
}
Json::object(fields)
}
///|
pub impl @json.FromJson for InputRequiredResult with fn from_json(
value : Json,
path : @json.JsonPath,
) -> InputRequiredResult raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
let input_requests : Map[String, InputRequest]? = match
fields.get("inputRequests") {
Some(Object(map)) => {
let requests : Map[String, InputRequest] = Map([])
for k, v in map {
requests[k] = @json.from_json(
v,
path=path.add_key("inputRequests").add_key(k),
)
}
Some(requests)
}
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("inputRequests"), "expected object"))
}
let request_state = match fields.get("requestState") {
Some(String(s)) => Some(s)
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("requestState"), "expected string"))
}
let meta : ResultMeta? = match fields.get("_meta") {
Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
None => None
}
InputRequiredResult(input_requests?, request_state?, meta?) catch {
error => raise JsonDecodeError((path, error.to_string()))
}
}
///|
pub fn InputRequiredResult::from_json(
value : Json,
path? : @json.JsonPath,
) -> InputRequiredResult raise McpError {
decode_json(value, path?)
}
///|
/// A typed handler can either finish or ask the client for more input.
pub(all) enum HandlerOutcome[T] {
Complete(T)
InputRequired(InputRequiredResult)
ExtensionResult(String, Json)
}
///|
/// Unwrap a Complete outcome or raise an error if more input is required or an extension result was returned.
pub fn[T] HandlerOutcome::require_complete(
self : HandlerOutcome[T],
context~ : String,
) -> T raise McpError {
match self {
Complete(result) => result
InputRequired(_) =>
raise InvalidRequest("\{context} cannot return input_required")
ExtensionResult(result_type, _) =>
raise InvalidRequest(
"\{context} cannot return extension result type \{result_type}",
)
}
}
///|
/// Decode a raw JSON-RPC response message into a HandlerOutcome.
pub fn HandlerOutcome::decode_response(
message : Json,
) -> HandlerOutcome[Json] raise McpError {
let decoded = decode_message(message)
let result = match decoded {
Response(Success(result~, ..)) => result
Response(Failure(error~, ..)) => raise McpError::from_error_object(error)
_ => raise InvalidRequest("JSON-RPC response expected")
}
match result {
Object({ "resultType": String("input_required"), .. }) =>
InputRequired(InputRequiredResult::from_json(result))
Object({ "resultType": String("complete"), .. }) => Complete(result)
Object({ "resultType": String(type_name), .. }) =>
ExtensionResult(type_name, result)
Object(_) => Complete(result)
_ => raise InvalidRequest("MCP result must be an object")
}
}