///|
/// MCP protocol version implemented by this SDK.
///
/// Single source of truth for the `MCP-Protocol-Version` header and the
/// `_meta.io.modelcontextprotocol/protocolVersion` field. Per the 2026-07-28
/// revision, every request carries its own version; there is no handshake.
pub const ProtocolVersion : String = "2026-07-28"

///|
pub(all) struct ServerInfo {
  name : String
  title : String?
  version : String
  description : String?
} derive(Debug, Eq)

///|
pub(all) struct ServerCapabilities {
  experimental : Map[String, Json]?
  logging : Map[String, Json]?
  completions : Map[String, Json]?
  tools : ToolCapabilities?
  resources : ResourceCapabilities?
  prompts : PromptCapabilities?
  extra_fields : Map[String, Json]
  /// Optional protocol extensions, per 2026-07-28 versioning: a map of
  /// extension identifiers (following `_meta` key naming rules) to
  /// per-extension settings objects.
  extensions : Map[String, Json]?
} derive(Debug, Eq)

///|
pub(all) struct ToolCapabilities {
  list_changed : Bool?
  extra_fields : Map[String, Json]
} derive(Debug, Eq)

///|
pub(all) struct ResourceCapabilities {
  subscribe : Bool?
  list_changed : Bool?
  extra_fields : Map[String, Json]
} derive(Debug, Eq)

///|
pub(all) struct PromptCapabilities {
  list_changed : Bool?
  extra_fields : Map[String, Json]
} derive(Debug, Eq)

///|
/// Request ID supporting exact JSON integers and strings. Int is a convenience
/// constructor; Number retains arbitrary-size wire integers.
pub(all) enum RequestId {
  Int(Int32)
  Number(JsonInteger)
  Str(String)
} derive(Debug)

///|
/// Convert RequestId to its JSON string representation
pub fn RequestId::to_json_string(self : RequestId) -> String {
  match self {
    Int(n) => n.to_string()
    Number(n) => n.to_json_string()
    // Delegate string quoting to the Json serializer so every JSON escape
    // (\b, \f, other control chars < 0x20) is emitted correctly; a
    // hand-rolled escape list could produce invalid JSON text.
    Str(s) => Json::string(s).stringify()
  }
}

///|
pub fn RequestId::to_json(self : RequestId) -> Json {
  match self {
    Int(value) => Json::number(value.to_double())
    Number(value) => value.to_json()
    Str(value) => Json::string(value)
  }
}

///|
pub fn RequestId::from_json(value : Json) -> RequestId raise MCPError {
  match value {
    String(value) => Str(value)
    Number(_, ..) => Number(JsonInteger::from_json(value))
    _ => raise InvalidRequest("Request id must be a string or JSON integer")
  }
}

///|
/// Type-tagged canonical identity for routing, duplicate checks and cancellation.
pub fn RequestId::key(self : RequestId) -> String {
  match self {
    Int(value) => "n:" + JsonInteger::from_int(value).key()
    Number(value) => "n:" + value.key()
    Str(value) => "s:" + value
  }
}

///|
pub impl Eq for RequestId with fn equal(self, other) {
  self.key() == other.key()
}

///|
pub(all) struct JsonRpcRequest {
  jsonrpc : String
  id : RequestId
  method_name : String
  params : Json
} derive(Debug, Eq)

///|
pub(all) struct PromptArgument {
  name : String
  title : String?
  description : String?
  required : Bool?
  extensions : Map[String, Json]
} derive(Debug, Eq)

///|
pub(all) struct PromptMessage {
  role : String
  content : ContentBlock
  extensions : Map[String, Json]
} derive(Debug, Eq)

///|
pub(all) struct GetPromptResult {
  description : String?
  messages : Array[PromptMessage]
  metadata : ResultMetadata
} derive(Debug, Eq)

///|
pub fn JsonRpcRequest::from_json(
  json : Json,
) -> Result[JsonRpcRequest, MCPError] {
  if json is Object(obj) {
    let jsonrpc = match obj.get("jsonrpc") {
      Some(String("2.0")) => "2.0"
      _ => return Err(InvalidRequest("Missing or invalid 'jsonrpc' field"))
    }
    let id = match obj.get("id") {
      Some(value) =>
        RequestId::from_json(value) catch {
          error => return Err(error)
        }
      None => return Err(InvalidRequest("Missing 'id' field"))
    }
    let method_name = match obj.get("method") {
      Some(String(m)) => m
      _ => return Err(InvalidRequest("Missing or invalid 'method' field"))
    }
    let params = obj.get("params").unwrap_or(null)
    Ok({ jsonrpc, id, method_name, params, })
  } else {
    Err(InvalidRequest("Request must be a JSON object"))
  }
}

///|
/// MRTR (Multi Round-Trip Requests) — 2026-07-28 spec basic/patterns/mrtr.
///
/// When a server needs client input (sampling/elicitation/roots) to finish a
/// request, it returns an `InputRequiredResult` whose `inputRequests` lists
/// what it needs and `requestState` carries server-side continuation state.
/// The client fulfills the inputs and retries the original request with
/// `inputResponses` + the echoed `requestState`.

///|
/// The three server→client input request methods. Servers MUST NOT send an
/// input request the client did not declare support for in its capabilities.
pub(all) enum InputRequestKind {
  ElicitationCreate
  SamplingCreateMessage
  RootsList
} derive(Eq, Debug)

///|
/// One entry in an `inputRequests` map: a server-assigned key plus the
/// request method and params the client must fulfill.
pub(all) struct InputRequestEntry {
  method_name : InputRequestKind
  params : Json
} derive(Eq, Debug)

///|
/// Convert an `InputRequestKind` to its JSON-RPC method string.
pub fn InputRequestKind::to_method(self : InputRequestKind) -> String {
  match self {
    ElicitationCreate => "elicitation/create"
    SamplingCreateMessage => "sampling/createMessage"
    RootsList => "roots/list"
  }
}

///|
/// Which kind of change notification a subscription filter should match.
/// Used internally by the server to route notifications to the right streams.
pub(all) enum SubscriptionNotificationKind {
  ToolsListChanged
  PromptsListChanged
  ResourcesListChanged
  /// A specific subscribed resource changed; matched against the
  /// subscription's `resourceSubscriptions` URI list.
  ResourceUpdated(uri~ : String)
} derive(Eq, Debug)

///|
/// Explicit trait method promotion for current MoonBit toolchains.
pub extend ServerInfo with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ServerInfo with Eq::{not_equal, equal}

///|
pub extend ServerCapabilities with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ServerCapabilities with Eq::{not_equal, equal}

///|
pub extend ToolCapabilities with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ToolCapabilities with Eq::{not_equal, equal}

///|
pub extend ResourceCapabilities with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ResourceCapabilities with Eq::{not_equal, equal}

///|
pub extend PromptCapabilities with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend PromptCapabilities with Eq::{not_equal, equal}

///|
pub extend ToolDefinition with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ToolDefinition with Eq::{not_equal, equal}

///|
pub extend RequestId with Eq::{not_equal, equal}

///|
pub extend RequestId with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend JsonRpcRequest with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend JsonRpcRequest with Eq::{not_equal, equal}

///|
pub extend PromptArgument with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend PromptArgument with Eq::{not_equal, equal}

///|
pub extend PromptMessage with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend PromptMessage with Eq::{not_equal, equal}

///|
pub extend GetPromptResult with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend GetPromptResult with Eq::{not_equal, equal}

///|
pub extend InputRequestKind with Eq::{not_equal, equal}

///|
pub extend InputRequestKind with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend InputRequestEntry with Eq::{not_equal, equal}

///|
pub extend InputRequestEntry with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend SubscriptionNotificationKind with Eq::{not_equal, equal}

///|
pub extend SubscriptionNotificationKind with @moonbitlang/core/debug.Debug::{
  to_repr,
}