Skip to main content
Glama

Convex MCP server

Official
by get-convex
action_outcome.rs12.3 kB
use anyhow::Context; use common::{ components::CanonicalizedComponentFunctionPath, errors::JsError, identity::InertIdentity, knobs::ISOLATE_MAX_USER_HEAP_SIZE, runtime::{ Runtime, UnixTimestamp, }, types::HttpActionRoute, value::ConvexArray, }; use pb::{ common::{ function_result::Result as FunctionResultTypeProto, FunctionResult as FunctionResultProto, }, outcome::{ ActionOutcome as ActionOutcomeProto, HttpActionOutcome as HttpActionOutcomeProto, }, }; #[cfg(any(test, feature = "testing"))] use proptest::prelude::*; use semver::Version; use value::JsonPackedValue; #[cfg(any(test, feature = "testing"))] use crate::HttpActionRequest; use crate::{ validation::ValidatedPathAndArgs, HttpActionRequestHead, SyscallTrace, }; #[derive(Debug, Clone)] #[cfg_attr( any(test, feature = "testing"), derive(proptest_derive::Arbitrary, PartialEq) )] pub enum HttpActionResult { Streamed, Error(JsError), } #[derive(Clone)] #[cfg_attr(any(test, feature = "testing"), derive(Debug, PartialEq))] pub struct ActionOutcome { pub path: CanonicalizedComponentFunctionPath, pub arguments: ConvexArray, pub identity: InertIdentity, pub unix_timestamp: UnixTimestamp, pub result: Result<JsonPackedValue, JsError>, pub syscall_trace: SyscallTrace, pub udf_server_version: Option<semver::Version>, } impl ActionOutcome { /// Used for synthesizing an outcome when we encounter an error before /// reaching the isolate. pub fn from_error( js_error: JsError, path: CanonicalizedComponentFunctionPath, arguments: ConvexArray, identity: InertIdentity, rt: impl Runtime, udf_server_version: Option<semver::Version>, ) -> Self { ActionOutcome { path, arguments, identity, unix_timestamp: rt.unix_timestamp(), result: Err(js_error), syscall_trace: SyscallTrace::new(), udf_server_version, } } pub(crate) fn from_proto( ActionOutcomeProto { unix_timestamp, result, syscall_trace, }: ActionOutcomeProto, path_and_args: ValidatedPathAndArgs, identity: InertIdentity, ) -> anyhow::Result<Self> { let result = result.ok_or_else(|| anyhow::anyhow!("Missing result"))?; let result = match result.result { Some(FunctionResultTypeProto::JsonPackedValue(value)) => { Ok(JsonPackedValue::from_network(value)?) }, Some(FunctionResultTypeProto::JsError(js_error)) => Err(js_error.try_into()?), None => anyhow::bail!("Missing result"), }; let (path, arguments, udf_server_version) = path_and_args.consume(); Ok(Self { path: path.for_logging(), arguments, identity, unix_timestamp: unix_timestamp .context("Missing unix_timestamp")? .try_into()?, result, syscall_trace: syscall_trace.context("Missing syscall_trace")?.try_into()?, udf_server_version, }) } } impl TryFrom<ActionOutcome> for ActionOutcomeProto { type Error = anyhow::Error; fn try_from( ActionOutcome { path: _, arguments: _, identity: _, unix_timestamp, result, syscall_trace, udf_server_version: _, }: ActionOutcome, ) -> anyhow::Result<Self> { let result = match result { Ok(value) => FunctionResultTypeProto::JsonPackedValue(value.as_str().to_string()), Err(js_error) => FunctionResultTypeProto::JsError(js_error.try_into()?), }; Ok(Self { unix_timestamp: Some(unix_timestamp.into()), result: Some(FunctionResultProto { result: Some(result), }), syscall_trace: Some(syscall_trace.try_into()?), }) } } #[cfg(any(test, feature = "testing"))] impl Arbitrary for ActionOutcome { type Parameters = (); type Strategy = impl Strategy<Value = ActionOutcome>; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { use proptest::prelude::*; ( any::<CanonicalizedComponentFunctionPath>(), any::<ConvexArray>(), any::<InertIdentity>(), any::<UnixTimestamp>(), any::<Result<JsonPackedValue, JsError>>(), any::<SyscallTrace>(), ) .prop_map( |(path, arguments, identity, unix_timestamp, result, syscall_trace)| Self { path, arguments, identity, unix_timestamp, result, syscall_trace, // Ok to not generate semver::Version because it is not serialized anyway udf_server_version: None, }, ) } } #[derive(Debug, Clone)] #[cfg_attr(any(test, feature = "testing"), derive(PartialEq))] pub struct HttpActionOutcome { pub route: HttpActionRoute, pub http_request: HttpActionRequestHead, pub identity: InertIdentity, pub unix_timestamp: UnixTimestamp, pub result: HttpActionResult, pub syscall_trace: SyscallTrace, pub udf_server_version: Option<semver::Version>, memory_in_mb: u64, } impl HttpActionOutcome { pub fn new( route: Option<HttpActionRoute>, http_request_head: HttpActionRequestHead, identity: InertIdentity, unix_timestamp: UnixTimestamp, result: HttpActionResult, syscall_trace: Option<SyscallTrace>, udf_server_version: Option<semver::Version>, ) -> Self { Self { route: route.unwrap_or(http_request_head.route_for_failure()), http_request: http_request_head, identity, unix_timestamp, result, syscall_trace: syscall_trace.unwrap_or_default(), udf_server_version, memory_in_mb: (*ISOLATE_MAX_USER_HEAP_SIZE / (1 << 20)) .try_into() .unwrap(), } } pub fn memory_in_mb(&self) -> u64 { self.memory_in_mb } pub(crate) fn from_proto( HttpActionOutcomeProto { unix_timestamp, result, syscall_trace, memory_in_mb, path, method, }: HttpActionOutcomeProto, http_request: HttpActionRequestHead, udf_server_version: Option<Version>, identity: InertIdentity, ) -> anyhow::Result<Self> { let result = result.ok_or_else(|| anyhow::anyhow!("Missing result"))?; let result = match result.result { Some(FunctionResultTypeProto::JsonPackedValue(_)) => { anyhow::bail!("Http actions not expected to have aresult") }, Some(FunctionResultTypeProto::JsError(js_error)) => { HttpActionResult::Error(js_error.try_into()?) }, None => HttpActionResult::Streamed, }; // TODO: Add `.context()` and remove fallback to `HttpRequestHead` let method = match method { Some(m) => m.parse()?, None => http_request.method.clone().try_into()?, }; let path = match path { Some(p) => p, None => http_request.url.to_string(), }; Ok(Self { identity, unix_timestamp: unix_timestamp .context("Missing unix_timestamp")? .try_into()?, result, syscall_trace: syscall_trace.context("Missing syscall_trace")?.try_into()?, memory_in_mb, http_request, udf_server_version, route: HttpActionRoute { method, path }, }) } } impl TryFrom<HttpActionOutcome> for HttpActionOutcomeProto { type Error = anyhow::Error; fn try_from( HttpActionOutcome { route, http_request: _, identity: _, unix_timestamp, result, syscall_trace, udf_server_version: _, memory_in_mb, }: HttpActionOutcome, ) -> anyhow::Result<Self> { let result = match result { HttpActionResult::Streamed => None, HttpActionResult::Error(js_error) => { Some(FunctionResultTypeProto::JsError(js_error.try_into()?)) }, }; Ok(Self { unix_timestamp: Some(unix_timestamp.into()), result: Some(FunctionResultProto { result }), syscall_trace: Some(syscall_trace.try_into()?), memory_in_mb, path: Some(route.path.to_string()), method: Some(route.method.to_string()), }) } } #[cfg(any(test, feature = "testing"))] impl Arbitrary for HttpActionOutcome { type Parameters = (); type Strategy = impl Strategy<Value = HttpActionOutcome>; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( any::<HttpActionRequest>(), any::<HttpActionResult>(), any::<InertIdentity>(), any::<UnixTimestamp>(), any::<SyscallTrace>(), any::<u64>(), ) .prop_map( |(request, result, identity, unix_timestamp, syscall_trace, memory_in_mb)| Self { http_request: request.head.clone(), result, route: HttpActionRoute { method: request.head.method.try_into().unwrap(), path: request.head.url.to_string(), }, identity, unix_timestamp, syscall_trace, memory_in_mb, // Ok to not generate semver::Version because it is not serialized anyway udf_server_version: None, }, ) } } #[cfg(test)] mod tests { use cmd_util::env::env_config; use proptest::prelude::*; use super::{ ActionOutcome, ActionOutcomeProto, HttpActionOutcomeProto, ValidatedPathAndArgs, }; use crate::HttpActionOutcome; proptest! { #![proptest_config( ProptestConfig { cases: 256 * env_config("CONVEX_PROPTEST_MULTIPLIER", 1), failure_persistence: None, ..ProptestConfig::default() } )] #[test] fn test_action_udf_outcome_roundtrips(udf_outcome in any::<ActionOutcome>()) { let udf_outcome_clone = udf_outcome.clone(); let path = udf_outcome.path.clone(); let arguments = udf_outcome.arguments.clone(); let version = udf_outcome.udf_server_version.clone(); let identity = udf_outcome_clone.identity.clone(); let path_and_args = ValidatedPathAndArgs::new_for_tests_in_component( path, arguments, version ); let proto = ActionOutcomeProto::try_from(udf_outcome_clone).unwrap(); let udf_outcome_from_proto = ActionOutcome::from_proto( proto, path_and_args, identity ).unwrap(); assert_eq!(udf_outcome, udf_outcome_from_proto); } #[test] fn test_http_action_outcome_roundtrips(udf_outcome in any::<HttpActionOutcome>()) { let udf_outcome_clone = udf_outcome.clone(); let http_request = udf_outcome.http_request.clone(); let version = udf_outcome.udf_server_version.clone(); let identity = udf_outcome_clone.identity.clone(); let proto = HttpActionOutcomeProto::try_from(udf_outcome_clone).unwrap(); let udf_outcome_from_proto = HttpActionOutcome::from_proto( proto, http_request, version, identity, ).unwrap(); assert_eq!(udf_outcome, udf_outcome_from_proto); } } }

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