Skip to main content
Glama

Convex MCP server

Official
by get-convex
roundtrip.rs6.31 kB
use std::collections::BTreeMap; use anyhow::Context; use serde_json::Value as JsonValue; use crate::Value; /// Type hint associated with a Convex value. This allows us to uniquely convert /// the exported value back to the original Convex value. #[allow(missing_docs)] pub enum ExportContext { Null, Int64, Float64 { // Store the f64 value in the export context when it is NaN, because the export format // assumes a single NaN value. This ensures that we can fully roundtrip values. nan_value: Option<f64>, }, Boolean, String, Bytes, Array(Vec<ExportContext>), Set, Map, Object(BTreeMap<String, ExportContext>), } impl ExportContext { /// Returns the export context of a Convex value pub fn of(value: &Value) -> ExportContext { match value { Value::Null => ExportContext::Null, Value::Int64(_) => ExportContext::Int64, Value::Float64(f) => ExportContext::Float64 { nan_value: f.is_nan().then_some(*f), }, Value::Boolean(_) => ExportContext::Boolean, Value::String(_) => ExportContext::String, Value::Bytes(_) => ExportContext::Bytes, Value::Array(elements) => { ExportContext::Array(elements.iter().map(ExportContext::of).collect()) }, Value::Object(fields) => ExportContext::Object( fields .iter() .map(|(key, value)| (key.clone(), ExportContext::of(value))) .collect(), ), } } } impl TryFrom<(JsonValue, &ExportContext)> for Value { type Error = anyhow::Error; fn try_from( (exported_value, type_hint): (JsonValue, &ExportContext), ) -> Result<Self, Self::Error> { match type_hint { ExportContext::Null => Ok(Value::Null), ExportContext::Int64 => match exported_value { JsonValue::String(str) => str .parse::<i64>() .map(Value::from) .context("Unexpected string for i64"), _ => anyhow::bail!("Unexpected value for i64"), }, ExportContext::Float64 { nan_value: Some(nan_value), } => { if !nan_value.is_nan() { anyhow::bail!("Unexpected non-NaN value in the export context"); } if exported_value != JsonValue::String(String::from("NaN")) { anyhow::bail!("Unexpected serialization of a NaN value"); } Ok((*nan_value).into()) }, ExportContext::Float64 { nan_value: None } => match exported_value { JsonValue::String(str) => match str.as_ref() { "Infinity" => Ok(f64::INFINITY.into()), "-Infinity" => Ok(f64::NEG_INFINITY.into()), _ => anyhow::bail!("Unexpected string for f64"), }, JsonValue::Number(n) => n .as_f64() .map(Value::from) .context("Unexpected number for i64"), _ => anyhow::bail!("Unexpected value for f64"), }, ExportContext::Boolean => match exported_value { JsonValue::Bool(value) => Ok(value.into()), _ => anyhow::bail!("Unexpected value for boolean"), }, ExportContext::String => match exported_value { JsonValue::String(value) => Ok(value.into()), _ => anyhow::bail!("Unexpected value for string"), }, ExportContext::Bytes => match exported_value { JsonValue::String(value) => base64::decode(value) .map(Value::from) .context("Unexpected string for bytes"), _ => anyhow::bail!("Unexpected value for bytes"), }, ExportContext::Array(type_hints) => match exported_value { JsonValue::Array(exported_values) => { if exported_values.len() != type_hints.len() { anyhow::bail!("Array lengths do not match"); } let values: anyhow::Result<Vec<Value>> = exported_values .into_iter() .zip(type_hints) .map(Value::try_from) .collect(); Ok(Value::Array(values?)) }, _ => anyhow::bail!("Unexpected value for array"), }, ExportContext::Set | ExportContext::Map => Value::try_from(exported_value) .context("Couldn’t deserialize set/map from internal representation"), ExportContext::Object(type_hints) => match exported_value { JsonValue::Object(exported_values) => { let entries: anyhow::Result<BTreeMap<String, Value>> = exported_values .into_iter() .map(|(key, value)| { let Some(type_hint) = type_hints.get(&key) else { anyhow::bail!("Missing export context for an object key"); }; Ok((key, (value, type_hint).try_into()?)) }) .collect(); Ok(Value::Object(entries?)) }, _ => anyhow::bail!("Unexpected value for object"), }, } } } #[cfg(test)] mod tests { use proptest::prelude::*; use crate::{ value::export::roundtrip::ExportContext, Value, }; proptest! { #![proptest_config(ProptestConfig { failure_persistence: None, ..ProptestConfig::default() })] #[test] fn export_roundtrips_with_type_hint(value in any::<Value>()) { let exported_value = value.clone().export(); let type_hint = ExportContext::of(&value); prop_assert_eq!( value, Value::try_from((exported_value, &type_hint)).unwrap() ); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server