Skip to main content
Glama

Convex MCP server

Official
by get-convex
analyze.rs13.7 kB
use std::{ collections::BTreeMap, sync::Arc, time::Duration, }; use common::types::{ ModuleEnvironment, UdfType, }; use model::{ config::types::ModuleConfig, udf_config::types::UdfConfig, }; use node_executor::local::LocalNodeExecutor; use runtime::prod::ProdRuntime; use crate::{ test_helpers::{ ApplicationFixtureArgs, ApplicationTestExt, }, tests::NODE_SOURCE, Application, }; const ISOLATE_SOURCE: &str = r#" export function isolateFunction() {} isolateFunction.isRegistered = true; isolateFunction.isQuery = true; isolateFunction.invokeQuery = () => {}; "#; const CRONS_SOURCE_A: &str = r#" export default { isCrons: true, export() { return JSON.stringify({ "an action": { name: "b:nodeFunction", args: [], schedule: { type: "interval", seconds: 1 }, }, }); }, }; "#; const CRONS_SOURCE_B: &str = r#" export default { isCrons: true, export() { return JSON.stringify({ "a mutation": { name: "a:isolateFunction", args: [], schedule: { type: "interval", seconds: 1 }, }, }); }, }; "#; // This test requires prod runtime since it analyzes node modules. #[convex_macro::prod_rt_test] async fn test_analyze(rt: ProdRuntime) -> anyhow::Result<()> { let application = Application::new_for_tests_with_args( &rt, ApplicationFixtureArgs::with_node_executor(Arc::new( LocalNodeExecutor::new(Duration::from_secs(10)).await?, )), ) .await?; let modules = vec![ ModuleConfig { path: "a.js".parse()?, source: ISOLATE_SOURCE.into(), source_map: None, environment: ModuleEnvironment::Isolate, }, ModuleConfig { path: "b.js".parse()?, source: NODE_SOURCE.into(), source_map: None, environment: ModuleEnvironment::Node, }, ]; let source_package = application.upload_package(&modules, None, None).await?; let udf_config = UdfConfig::new_for_test(&rt, "1000.0.0".parse()?); let modules = application .analyze( udf_config, modules, source_package, BTreeMap::new(), BTreeMap::new(), ) .await??; assert_eq!(modules.len(), 2); let a_path = "a.js".parse()?; assert_eq!(modules[&a_path].functions.len(), 1); let module = &modules[&a_path].functions[0]; assert_eq!(module.udf_type, UdfType::Query); assert_eq!(&module.name[..], "isolateFunction"); assert!(module.pos.is_none()); let b_path = "b.js".parse()?; assert_eq!(modules[&b_path].functions.len(), 1); let module = &modules[&b_path].functions[0]; assert_eq!(module.udf_type, UdfType::Action); assert_eq!(&module.name[..], "nodeFunction"); assert!(module.pos.is_none()); Ok(()) } #[convex_macro::prod_rt_test] async fn test_analyze_with_source_map(rt: ProdRuntime) -> anyhow::Result<()> { const SAMPLE_SOURCE: &str = r#" async function invokeAction(func, requestId, argsStr) { throw new Error("unimplemented"); } var actionGeneric = func => { const q = func; if (q.isRegistered) { throw new Error("Function registered twice " + func); } q.isRegistered = true; q.isAction = true; q.isPublic = true; q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr); return q; }; var internalActionGeneric = func => { const q = func; if (q.isRegistered) { throw new Error("Function registered twice " + func); } q.isRegistered = true; q.isAction = true; q.isInternal = true; q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr); return q; }; var action = actionGeneric; var internalAction = internalActionGeneric; var hello = action(async ({}) => { console.log("analyze me pls"); }); var internalHello = internalAction(async ({}) => { console.log("analyze me pls"); }); export { hello, internalHello }; "#; // Generated via `npx esbuild static_node_source.js --bundle --format=esm // --target=esnext --sourcemap=linked --outfile=out.js` const NODE_SOURCE_MAP: &str = r#" { "version": 3, "sources": ["node_source.js"], "sourcesContent": ["async function invokeAction(func, requestId, argsStr) {\n throw new Error(\"unimplemented\");\n}\nvar actionGeneric = func => {\n const q = func;\n if (q.isRegistered) {\n throw new Error(\"Function registered twice \" + func);\n }\n q.isRegistered = true;\n q.isAction = true;\n q.isPublic = true;\n q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr);\n return q;\n};\nvar internalActionGeneric = func => {\n const q = func;\n if (q.isRegistered) {\n throw new Error(\"Function registered twice \" + func);\n }\n q.isRegistered = true;\n q.isAction = true;\n q.isInternal = true;\n q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr);\n return q;\n };\nvar action = actionGeneric;\nvar internalAction = internalActionGeneric;\nvar hello = action(async ({}) => {\n console.log(\"analyze me pls\");\n});\nvar internalHello = internalAction(async ({}) => {\n console.log(\"analyze me pls\");\n});\nexport { hello, internalHello };\n"], "mappings": ";AAAA,eAAe,aAAa,MAAM,WAAW,SAAS;AACpD,QAAM,IAAI,MAAM,eAAe;AACjC;AACA,IAAI,gBAAgB,UAAQ;AAC1B,QAAM,IAAI;AACV,MAAI,EAAE,cAAc;AAClB,UAAM,IAAI,MAAM,+BAA+B,IAAI;AAAA,EACrD;AACA,IAAE,eAAe;AACjB,IAAE,WAAW;AACb,IAAE,WAAW;AACb,IAAE,eAAe,CAAC,WAAW,YAAY,aAAa,MAAM,WAAW,OAAO;AAC9E,SAAO;AACT;AACA,IAAI,wBAAwB,UAAQ;AAChC,QAAM,IAAI;AACV,MAAI,EAAE,cAAc;AAClB,UAAM,IAAI,MAAM,+BAA+B,IAAI;AAAA,EACrD;AACA,IAAE,eAAe;AACjB,IAAE,WAAW;AACb,IAAE,aAAa;AACf,IAAE,eAAe,CAAC,WAAW,YAAY,aAAa,MAAM,WAAW,OAAO;AAC9E,SAAO;AACT;AACF,IAAI,SAAS;AACb,IAAI,iBAAiB;AACrB,IAAI,QAAQ,OAAO,OAAO,CAAC,MAAM;AAC/B,UAAQ,IAAI,gBAAgB;AAC9B,CAAC;AACD,IAAI,gBAAgB,eAAe,OAAO,CAAC,MAAM;AAC/C,UAAQ,IAAI,gBAAgB;AAC9B,CAAC;", "names": [] } "#; const ISOLATE_SOURCE_MAP: &str = r#" { "version": 3, "sources": ["isolate_source.js"], "sourcesContent": ["async function invokeAction(func, requestId, argsStr) {\n throw new Error(\"unimplemented\");\n}\nvar actionGeneric = func => {\n const q = func;\n if (q.isRegistered) {\n throw new Error(\"Function registered twice \" + func);\n }\n q.isRegistered = true;\n q.isAction = true;\n q.isPublic = true;\n q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr);\n return q;\n};\nvar internalActionGeneric = func => {\n const q = func;\n if (q.isRegistered) {\n throw new Error(\"Function registered twice \" + func);\n }\n q.isRegistered = true;\n q.isAction = true;\n q.isInternal = true;\n q.invokeAction = (requestId, argsStr) => invokeAction(func, requestId, argsStr);\n return q;\n };\nvar action = actionGeneric;\nvar internalAction = internalActionGeneric;\nvar hello = action(async ({}) => {\n console.log(\"analyze me pls\");\n});\nvar internalHello = internalAction(async ({}) => {\n console.log(\"analyze me pls\");\n});\nexport { hello, internalHello };\n"], "mappings": ";AAAA,eAAe,aAAa,MAAM,WAAW,SAAS;AACpD,QAAM,IAAI,MAAM,eAAe;AACjC;AACA,IAAI,gBAAgB,UAAQ;AAC1B,QAAM,IAAI;AACV,MAAI,EAAE,cAAc;AAClB,UAAM,IAAI,MAAM,+BAA+B,IAAI;AAAA,EACrD;AACA,IAAE,eAAe;AACjB,IAAE,WAAW;AACb,IAAE,WAAW;AACb,IAAE,eAAe,CAAC,WAAW,YAAY,aAAa,MAAM,WAAW,OAAO;AAC9E,SAAO;AACT;AACA,IAAI,wBAAwB,UAAQ;AAChC,QAAM,IAAI;AACV,MAAI,EAAE,cAAc;AAClB,UAAM,IAAI,MAAM,+BAA+B,IAAI;AAAA,EACrD;AACA,IAAE,eAAe;AACjB,IAAE,WAAW;AACb,IAAE,aAAa;AACf,IAAE,eAAe,CAAC,WAAW,YAAY,aAAa,MAAM,WAAW,OAAO;AAC9E,SAAO;AACT;AACF,IAAI,SAAS;AACb,IAAI,iBAAiB;AACrB,IAAI,QAAQ,OAAO,OAAO,CAAC,MAAM;AAC/B,UAAQ,IAAI,gBAAgB;AAC9B,CAAC;AACD,IAAI,gBAAgB,eAAe,OAAO,CAAC,MAAM;AAC/C,UAAQ,IAAI,gBAAgB;AAC9B,CAAC;", "names": [] } "#; let application = Application::new_for_tests_with_args( &rt, ApplicationFixtureArgs::with_node_executor(Arc::new( LocalNodeExecutor::new(Duration::from_secs(10)).await?, )), ) .await?; let modules = vec![ ModuleConfig { path: "isolate_source.js".parse()?, source: SAMPLE_SOURCE.into(), source_map: Some(ISOLATE_SOURCE_MAP.to_string()), environment: ModuleEnvironment::Isolate, }, ModuleConfig { path: "node_source.js".parse()?, source: SAMPLE_SOURCE.into(), source_map: Some(NODE_SOURCE_MAP.to_string()), environment: ModuleEnvironment::Node, }, ]; let source_package = application.upload_package(&modules, None, None).await?; let udf_config = UdfConfig::new_for_test(&rt, "1000.0.0".parse()?); let modules = application .analyze( udf_config.clone(), modules, source_package, BTreeMap::new(), BTreeMap::new(), ) .await??; assert_eq!(modules.len(), 2); let isolate_path = "isolate_source.js".parse()?; assert_eq!(modules[&isolate_path].functions.len(), 2); let module = &modules[&isolate_path]; assert_eq!(&module.functions[0].name[..], "hello"); assert_eq!(module.functions[0].udf_type, UdfType::Action); assert_eq!(module.functions[0].pos.as_ref().unwrap().start_lineno, 28); assert_eq!(&module.functions[1].name[..], "internalHello"); assert_eq!(module.functions[1].udf_type, UdfType::Action); assert_eq!(module.functions[1].pos.as_ref().unwrap().start_lineno, 31); let node_path = "node_source.js".parse()?; assert_eq!(modules[&node_path].functions.len(), 2); let module = &modules[&node_path]; assert_eq!(&module.functions[0].name[..], "hello"); assert_eq!(module.functions[0].udf_type, UdfType::Action); assert_eq!(module.functions[0].pos.as_ref().unwrap().start_lineno, 28); assert_eq!(&module.functions[1].name[..], "internalHello"); assert_eq!(module.functions[1].udf_type, UdfType::Action); assert_eq!(module.functions[1].pos.as_ref().unwrap().start_lineno, 31); Ok(()) } // This test requires prod runtime since it analyzes node modules. #[convex_macro::prod_rt_test] async fn test_analyze_crons(rt: ProdRuntime) -> anyhow::Result<()> { let application = Application::new_for_tests_with_args( &rt, ApplicationFixtureArgs::with_node_executor(Arc::new( LocalNodeExecutor::new(Duration::from_secs(10)).await?, )), ) .await?; let modules = vec![ ModuleConfig { path: "a.js".parse()?, source: ISOLATE_SOURCE.into(), source_map: None, environment: ModuleEnvironment::Isolate, }, ModuleConfig { path: "b.js".parse()?, source: NODE_SOURCE.into(), source_map: None, environment: ModuleEnvironment::Node, }, ModuleConfig { path: "crons.js".parse()?, source: CRONS_SOURCE_A.into(), source_map: None, environment: ModuleEnvironment::Isolate, }, ]; let source_package = application.upload_package(&modules, None, None).await?; let udf_config = UdfConfig::new_for_test(&rt, "1000.0.0".parse()?); let modules = application .analyze( udf_config.clone(), modules, source_package, BTreeMap::new(), BTreeMap::new(), ) .await??; assert_eq!(modules.len(), 3); let a_path = "a.js".parse()?; assert_eq!(modules[&a_path].functions.len(), 1); let module = &modules[&a_path].functions[0]; assert_eq!(module.udf_type, UdfType::Query); assert_eq!(&module.name[..], "isolateFunction"); assert!(module.pos.is_none()); let b_path = "b.js".parse()?; assert_eq!(modules[&b_path].functions.len(), 1); let module = &modules[&b_path].functions[0]; assert_eq!(module.udf_type, UdfType::Action); assert_eq!(&module.name[..], "nodeFunction"); assert!(module.pos.is_none()); let application = Application::new_for_tests(&rt).await?; let modules = vec![ModuleConfig { path: "crons.js".parse()?, source: CRONS_SOURCE_A.into(), source_map: None, environment: ModuleEnvironment::Isolate, }]; let source_package = application.upload_package(&modules, None, None).await?; let result = application .analyze( udf_config.clone(), modules, source_package, BTreeMap::new(), BTreeMap::new(), ) .await; let Err(err) = result else { anyhow::bail!("No JsError raised for scheduled nonexistent function"); }; assert!( format!("{err}").contains("schedules a function that does not exist"), "{err:?}" ); let application = Application::new_for_tests(&rt).await?; let modules = vec![ModuleConfig { path: "crons.js".parse()?, source: CRONS_SOURCE_B.into(), source_map: None, environment: ModuleEnvironment::Isolate, }]; let source_package = application.upload_package(&modules, None, None).await?; let result = application .analyze( udf_config, modules, source_package, BTreeMap::new(), BTreeMap::new(), ) .await; let Err(err) = result else { anyhow::bail!("No JsError raised for scheduled nonexistent function"); }; assert!( format!("{err}").contains("schedules a function that does not exist"), "{err:?}" ); 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