Skip to main content
Glama

Convex MCP server

Official
by get-convex
mod.rs6.48 kB
#[cfg(test)] mod tests; #[cfg(test)] mod index_diff_tests; #[cfg(test)] mod index_limits_tests; #[cfg(test)] pub mod index_test_utils; #[cfg(test)] mod index_tests; pub mod module_loader; pub mod types; use std::collections::BTreeMap; use common::{ components::ComponentId, document::ParsedDocument, runtime::Runtime, schemas::DatabaseSchema, }; use database::{ unauthorized_error, IndexModel, SchemaModel, Transaction, }; use sync_types::CanonicalizedModulePath; use value::ResolvedDocumentId; use self::module_loader::ModuleLoader; use crate::{ auth::AuthInfoModel, config::types::{ ConfigDiff, ConfigMetadata, ModuleConfig, AUTH_CONFIG_FILE_NAME, }, cron_jobs::CronModel, modules::{ module_versions::AnalyzedModule, types::ModuleMetadata, ModuleModel, }, source_packages::{ types::SourcePackage, SourcePackageModel, }, udf_config::{ types::UdfConfig, UdfConfigModel, }, }; pub struct ConfigModel<'a, RT: Runtime> { pub tx: &'a mut Transaction<RT>, component: ComponentId, } impl<'a, RT: Runtime> ConfigModel<'a, RT> { pub fn new(tx: &'a mut Transaction<RT>, component: ComponentId) -> Self { Self { tx, component } } #[fastrace::trace] pub async fn apply( &mut self, config: ConfigMetadata, modules: Vec<ModuleConfig>, new_config: UdfConfig, source_package: Option<SourcePackage>, analyze_results: BTreeMap<CanonicalizedModulePath, AnalyzedModule>, schema_id: Option<ResolvedDocumentId>, ) -> anyhow::Result<(ConfigDiff, Option<DatabaseSchema>)> { // TODO: Move this check up to `Application`. if !(self.tx.identity().is_admin() || self.tx.identity().is_system()) { anyhow::bail!(unauthorized_error("apply_config")); } let source_package_id = match source_package { Some(source_package) => Some( SourcePackageModel::new(self.tx, self.component.into()) .put(source_package) .await?, ), None => None, }; let cron_diff = CronModel::new(self.tx, self.component) .apply(&analyze_results) .await?; let (schema_diff, next_schema) = SchemaModel::new(self.tx, self.component.into()) .apply(schema_id) .await?; let index_diff = IndexModel::new(self.tx) .apply(self.component.into(), &next_schema) .await?; let module_diff = ModuleModel::new(self.tx) .apply(self.component, modules, source_package_id, analyze_results) .await?; // Update auth info. let auth_diff = AuthInfoModel::new(self.tx).put(config.auth_info).await?; let udf_server_version_diff = UdfConfigModel::new(self.tx, self.component.into()) .set(new_config) .await?; let config_diff = ConfigDiff { module_diff, auth_diff, udf_server_version_diff, cron_diff, // TODO(CX-3851): Consider logging the mutated indexes separately. // This now includes added, mutated and dropped indexes. Mutated // indexes are shown both in 'added' and in 'dropped' index_diff: index_diff.into(), schema_diff, }; Ok((config_diff, next_schema)) } /// Return the latest database configuration. This includes only the /// user-configurable state and not internal derived state like shapes. We /// might want to store this config in memory but for now just reading it /// out of the metadata tables to avoid keeping too many sources of truth. pub async fn get_with_module_source( &mut self, module_loader: &dyn ModuleLoader<RT>, ) -> anyhow::Result<(ConfigMetadata, Vec<ModuleConfig>, Option<UdfConfig>)> { // TODO: Move to `application/`. if !(self.tx.identity().is_admin() || self.tx.identity().is_system()) { anyhow::bail!(unauthorized_error("get_config")); } let mut config = ConfigMetadata::new(); let modules: Vec<_> = ModuleModel::new(self.tx) .get_application_modules(self.component, module_loader) .await? .into_values() .collect(); // If we have an auth config module do not include auth_info in the config if !modules .iter() .any(|module| module.path == AUTH_CONFIG_FILE_NAME.parse().unwrap()) { let auth_info = AuthInfoModel::new(self.tx).get().await?; config.auth_info = auth_info.into_iter().map(|doc| doc.into_value()).collect(); } let udf_config = UdfConfigModel::new(self.tx, self.component.into()) .get() .await? .map(|u| (**u).clone()); Ok((config, modules, udf_config)) } /// Return the latest database configuration. This includes only the /// user-configurable state and not internal derived state like shapes. We /// might want to store this config in memory but for now just reading it /// out of the metadata tables to avoid keeping too many sources of truth. pub async fn get_with_module_metadata( &mut self, ) -> anyhow::Result<( ConfigMetadata, Vec<ParsedDocument<ModuleMetadata>>, Option<UdfConfig>, )> { // TODO: Move to `application/`. if !(self.tx.identity().is_admin() || self.tx.identity().is_system()) { anyhow::bail!(unauthorized_error("get_config")); } let mut config = ConfigMetadata::new(); let modules = ModuleModel::new(self.tx) .get_application_metadata(self.component) .await?; // If we have an auth config module do not include auth_info in the config if !modules .iter() .any(|module| module.path == AUTH_CONFIG_FILE_NAME.parse().unwrap()) { let auth_info = AuthInfoModel::new(self.tx).get().await?; config.auth_info = auth_info.into_iter().map(|doc| doc.into_value()).collect(); } let udf_config = UdfConfigModel::new(self.tx, self.component.into()) .get() .await? .map(|u| (**u).clone()); Ok((config, modules, udf_config)) } }

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