Skip to main content
Glama

Convex MCP server

Official
by get-convex
helpers.rs9.46 kB
use anyhow::Context; use common::{ components::ResolvedComponentFunctionPath, errors::{ report_error_sync, JsError, }, knobs::FUNCTION_MAX_RESULT_SIZE, value::ConvexValue, }; use deno_core::v8; use errors::{ ErrorMetadata, ErrorMetadataAnyhowExt, }; use humansize::{ FormatSize, BINARY, }; use serde::Deserialize; use serde_json::value::RawValue; use sync_types::types::SerializedArgs; use value::Size; use crate::{ metrics::log_legacy_positional_args, strings, }; // The below methods were taken from `deno_core` // https://github.com/denoland/deno_core/blob/main/LICENSE.md - MIT License // Copyright 2018-2024 the Deno authors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /// Taken from `deno_core::bindings::module_origin`. pub fn module_origin<'a>( s: &mut v8::HandleScope<'a>, resource_name: v8::Local<'a, v8::String>, ) -> v8::ScriptOrigin<'a> { // TODO: Fill this out more accurately. let source_map_url = strings::empty.create(s).unwrap(); v8::ScriptOrigin::new( s, resource_name.into(), // resource_name 0, // resource_line_offset 0, // resource_column_offset false, // resource_is_shared_cross_origin 0, // script_id Some(source_map_url.into()), // source_map_url true, // resource_is_opaque false, // is_wasm true, // is_module None, // host_defined_options ) } /// Run all queued tasks from this isolate's foreground task runner. /// In particular, this runs minor GC tasks that are scheduled. pub fn pump_message_loop(isolate: &mut v8::Isolate) { let platform = v8::V8::get_current_platform(); while v8::Platform::pump_message_loop(&platform, isolate, false /* wait_for_work */) {} } /// Taken from `deno_core::bindings::throw_type_error`. pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) { let message = v8::String::new(scope, message.as_ref()).unwrap(); let exception = v8::Exception::type_error(scope, message); scope.throw_exception(exception); } pub fn to_rust_string(scope: &mut v8::Isolate, s: &v8::String) -> anyhow::Result<String> { let n = s.utf8_length(scope); let mut buf = vec![0; n]; // Don't set `kReplaceInvalidUtf8` since we want unpaired surrogates to fail // the UTF8 check below. let num_written = s.write_utf8_v2(scope, &mut buf, v8::WriteFlags::empty(), None); anyhow::ensure!(n == num_written); let s = String::from_utf8(buf)?; Ok(s) } pub fn get_property<'a>( scope: &mut v8::HandleScope<'a>, object: v8::Local<v8::Object>, key: &str, ) -> anyhow::Result<Option<v8::Local<'a, v8::Value>>> { let key = v8::String::new(scope, key) .ok_or_else(|| anyhow::anyhow!("Failed to create string for {key}"))?; Ok(object.get(scope, key.into())) } pub fn deserialize_udf_result( path: &ResolvedComponentFunctionPath, result_str: &str, ) -> anyhow::Result<Result<ConvexValue, JsError>> { // Don't print out result_str in error messages - as it may contain pii let result_v: serde_json::Value = serde_json::from_str(result_str).map_err(|e| { anyhow::anyhow!(ErrorMetadata::bad_request( "FunctionReturnInvalidJson", format!( "Function {} failed. Could not parse return value as json: {e}", path.clone().for_logging().debug_str() ), )) })?; let result = match ConvexValue::try_from(result_v) { Ok(value) => { if value.size() > *FUNCTION_MAX_RESULT_SIZE { Err(JsError::from_message(format!( "Function {} return value is too large (actual: {}, limit: {})", path.clone().for_logging().debug_str(), value.size().format_size(BINARY), (*FUNCTION_MAX_RESULT_SIZE).format_size(BINARY), ))) } else { Ok(value) } }, Err(e) if e.is_deterministic_user_error() => { Err(JsError::from_error(e.wrap_error_message(|msg| { format!( "Function {} return value invalid: {msg}", path.clone().for_logging().debug_str() ) }))) }, Err(e) => return Err(e), }; Ok(result) } // custom error is called `ConvexError` in udfs pub fn deserialize_udf_custom_error( message: String, serialized_data: Option<String>, ) -> anyhow::Result<(String, Option<ConvexValue>)> { Ok(if let Some(serialized_data) = serialized_data { let deserialized_custom_data = deserialize_udf_custom_error_data(&serialized_data)?; match deserialized_custom_data { Ok(custom_data) => (message, Some(custom_data)), // If we can't deserialize the custom data, we'll replace // the ConvexError with the formatting error Err(custom_data_format_error) => (custom_data_format_error.message, None), } } else { (message, None) }) } fn deserialize_udf_custom_error_data( result_str: &str, ) -> anyhow::Result<Result<ConvexValue, JsError>> { let result_v: serde_json::Value = serde_json::from_str(result_str).context(format!( "Unable to deserialize udf error data: {result_str}" ))?; let result = match ConvexValue::try_from(result_v) { Ok(value) => Ok(value), Err(e) if e.is_deterministic_user_error() => { Err(JsError::from_error(e.wrap_error_message(|msg| { format!("ConvexError with invalid data: {msg}") }))) }, Err(e) => return Err(e), }; Ok(result) } pub fn format_uncaught_error(message: String, name: String) -> String { if !name.is_empty() && !message.is_empty() { format!("Uncaught {name}: {message}") } else if !name.is_empty() { format!("Uncaught {name}") } else if !message.is_empty() { format!("Uncaught {message}") } else { "Uncaught".to_string() } } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct UdfArgsJson(Box<RawValue>); impl UdfArgsJson { /// Map it into our internal representation of positional args. /// Modern apps just have a single positional arg with an object. pub fn into_serialized_args(self) -> anyhow::Result<SerializedArgs> { // For legacy positional args array // RawValue from serde is guaranteed to have no leading whitespace. if self.0.get().starts_with("[") { log_legacy_positional_args(); return Ok(SerializedArgs(self.0)); } // For named args - stick it in an array Ok(SerializedArgs(serde_json::value::to_raw_value(&[self.0])?)) } } pub fn source_map_from_slice(slice: &[u8]) -> Option<sourcemap::SourceMap> { // If the source map doesn't parse, report the parsing error but don't fail // the entire request, just treat it like a missing source map. match sourcemap::SourceMap::from_slice(slice).context(ErrorMetadata::bad_request( "BadSourceMap", "could not parse source map", )) { Ok(source_map) => Some(source_map), Err(mut e) => { report_error_sync(&mut e); None }, } } #[cfg(test)] mod tests { use serde_json::json; use sync_types::types::SerializedArgs; use crate::UdfArgsJson; #[test] fn test_udf_args_json() -> anyhow::Result<()> { let json1: UdfArgsJson = serde_json::from_str(r#"["a","b","c"]"#)?; let json2: UdfArgsJson = serde_json::from_str(r#"{"named":"arg"}"#)?; let json3: UdfArgsJson = serde_json::from_str(r#"[{"named":"arg"}]"#)?; assert_eq!( json1.into_serialized_args()?, SerializedArgs::from_args(vec![json!("a"), json!("b"), json!("c")])?, ); assert_eq!( json2.into_serialized_args()?, SerializedArgs::from_args(vec![json!({"named": "arg"})])?, ); assert_eq!( json3.into_serialized_args()?, SerializedArgs::from_args(vec![json!({"named": "arg"})])?, ); Ok(()) } }

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