use serde_json::{Map, Value, json};
use super::framing::{INVALID_PARAMS, UNSUPPORTED_PROTOCOL_VERSION};
pub const MODERN: &str = "2026-07-28";
pub const LEGACY: &str = "2025-11-25";
pub const SUPPORTED: [&str; 2] = [MODERN, LEGACY];
const META: &str = "_meta";
const META_PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
const META_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
const META_SERVER_INFO: &str = "io.modelcontextprotocol/serverInfo";
#[must_use]
pub fn server_info() -> Value {
json!({ "name": "liminal", "version": env!("CARGO_PKG_VERSION") })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Era {
Modern,
Legacy,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Refusal {
pub code: i64,
pub message: String,
pub data: Option<Value>,
}
#[derive(Debug, Default)]
pub struct Session {
legacy: bool,
}
impl Session {
#[must_use]
pub const fn new() -> Self {
Self { legacy: false }
}
#[must_use]
pub const fn is_legacy(&self) -> bool {
self.legacy
}
pub fn era_of(&self, params: &Map<String, Value>) -> Result<Era, Refusal> {
let meta = params.get(META).and_then(Value::as_object);
let version = meta.and_then(|meta| meta.get(META_PROTOCOL_VERSION));
match version {
Some(Value::String(version)) if version == MODERN => {
if meta
.and_then(|meta| meta.get(META_CLIENT_CAPABILITIES))
.is_none()
{
return Err(invalid_params(META_CLIENT_CAPABILITIES));
}
Ok(Era::Modern)
}
Some(Value::String(version)) => Err(unsupported(version)),
None if self.legacy => Ok(Era::Legacy),
Some(_) | None => Err(invalid_params(META_PROTOCOL_VERSION)),
}
}
pub fn initialize(&mut self, params: &Map<String, Value>) -> Result<Value, Refusal> {
params
.get("protocolVersion")
.and_then(Value::as_str)
.ok_or_else(|| invalid_params("protocolVersion"))?;
self.legacy = true;
Ok(json!({
"protocolVersion": LEGACY,
"capabilities": { "tools": {}, "logging": {} },
"serverInfo": server_info(),
}))
}
#[must_use]
pub fn discover() -> Value {
json!({
"resultType": "complete",
"supportedVersions": [MODERN],
"capabilities": { "tools": {}, "logging": {} },
META: { META_SERVER_INFO: server_info() },
"instructions": "The ticket to a liminal network: list the stations, describe one, call one by its subject, or run a flow of calls. What call may reach is the pass's grant.",
})
}
}
#[must_use]
pub fn shaped(era: Era, mut result: Map<String, Value>) -> Value {
if era == Era::Modern {
result.insert("resultType".to_owned(), json!("complete"));
result.insert(META.to_owned(), json!({ META_SERVER_INFO: server_info() }));
}
Value::Object(result)
}
fn unsupported(requested: &str) -> Refusal {
Refusal {
code: UNSUPPORTED_PROTOCOL_VERSION,
message: "Unsupported protocol version".to_owned(),
data: Some(json!({ "supported": SUPPORTED, "requested": requested })),
}
}
fn invalid_params(field: &str) -> Refusal {
Refusal {
code: INVALID_PARAMS,
message: format!("the request is missing {field}"),
data: None,
}
}
#[cfg(test)]
#[path = "negotiate_tests.rs"]
mod tests;