Skip to main content
Glama

Convex MCP server

Official
by get-convex
mod.rs16.3 kB
pub mod definition; use std::{ collections::BTreeMap, sync::{ Arc, LazyLock, }, }; use anyhow::Context; use common::{ bootstrap_model::components::{ definition::{ ComponentDefinitionMetadata, ComponentDefinitionType, }, ComponentMetadata, ComponentType, }, components::{ CanonicalizedComponentFunctionPath, CanonicalizedComponentModulePath, ComponentDefinitionId, ComponentDefinitionPath, ComponentId, ComponentName, ComponentPath, Resource, }, document::{ ParseDocument, ParsedDocument, }, query::{ Order, Query, }, runtime::Runtime, }; use errors::ErrorMetadata; use value::{ identifier::Identifier, ConvexValue, DeveloperDocumentId, FieldPath, ResolvedDocumentId, TableName, TableNamespace, }; use crate::{ metrics, system_tables::{ SystemIndex, SystemTable, }, ComponentDefinitionsTable, ResolvedQuery, Transaction, COMPONENT_DEFINITIONS_TABLE, }; pub static COMPONENTS_TABLE: LazyLock<TableName> = LazyLock::new(|| { "_components" .parse() .expect("Invalid built-in _components table") }); pub static COMPONENTS_BY_PARENT_INDEX: LazyLock<SystemIndex<ComponentsTable>> = LazyLock::new(|| SystemIndex::new("by_parent_and_name", [&PARENT_FIELD, &NAME_FIELD]).unwrap()); pub static PARENT_FIELD: LazyLock<FieldPath> = LazyLock::new(|| "parent".parse().unwrap()); pub static NAME_FIELD: LazyLock<FieldPath> = LazyLock::new(|| "name".parse().unwrap()); pub struct ComponentsTable; impl SystemTable for ComponentsTable { type Metadata = ComponentMetadata; fn table_name() -> &'static TableName { &COMPONENTS_TABLE } fn indexes() -> Vec<SystemIndex<Self>> { vec![COMPONENTS_BY_PARENT_INDEX.clone()] } } pub struct BootstrapComponentsModel<'a, RT: Runtime> { pub tx: &'a mut Transaction<RT>, } impl<'a, RT: Runtime> BootstrapComponentsModel<'a, RT> { pub fn new(tx: &'a mut Transaction<RT>) -> Self { Self { tx } } pub fn component_in_parent( &mut self, parent_and_name: Option<(DeveloperDocumentId, ComponentName)>, ) -> anyhow::Result<Option<ParsedDocument<ComponentMetadata>>> { self.tx .component_registry .component_in_parent(parent_and_name, &mut self.tx.reads) } pub fn component_children( &mut self, parent_id: DeveloperDocumentId, ) -> anyhow::Result<Vec<ParsedDocument<ComponentMetadata>>> { self.tx .component_registry .component_children(parent_id, &mut self.tx.reads) } pub fn root_component(&mut self) -> anyhow::Result<Option<ParsedDocument<ComponentMetadata>>> { self.component_in_parent(None) } pub fn resolve_path( &mut self, path: &ComponentPath, ) -> anyhow::Result<Option<ParsedDocument<ComponentMetadata>>> { self.tx .component_registry .resolve_path(path, &mut self.tx.reads) } pub fn all_component_paths(&mut self) -> BTreeMap<ComponentId, ComponentPath> { self.tx .component_registry .all_component_paths(&mut self.tx.reads) } #[fastrace::trace] pub async fn load_all_components( &mut self, ) -> anyhow::Result<Vec<ParsedDocument<ComponentMetadata>>> { let mut query = ResolvedQuery::new( self.tx, TableNamespace::Global, Query::full_table_scan(COMPONENTS_TABLE.clone(), Order::Asc), )?; let mut components = Vec::new(); while let Some(doc) = query.next(self.tx, None).await? { components.push(doc.parse()?); } Ok(components) } pub fn resolve_component_id( &mut self, component_id: DeveloperDocumentId, ) -> anyhow::Result<ResolvedDocumentId> { let component_table = self .tx .table_mapping() .namespace(TableNamespace::Global) .id(&COMPONENTS_TABLE)?; Ok(ResolvedDocumentId::new( component_table.tablet_id, component_id, )) } pub fn resolve_component_definition_id( &mut self, component_definition_id: DeveloperDocumentId, ) -> anyhow::Result<ResolvedDocumentId> { let component_definitions_table = self .tx .table_mapping() .namespace(TableNamespace::Global) .id(&COMPONENT_DEFINITIONS_TABLE)?; Ok(ResolvedDocumentId::new( component_definitions_table.tablet_id, component_definition_id, )) } pub fn get_component_path(&mut self, component_id: ComponentId) -> Option<ComponentPath> { self.tx.get_component_path(component_id) } pub fn must_component_path( &mut self, component_id: ComponentId, ) -> anyhow::Result<ComponentPath> { self.get_component_path(component_id) .with_context(|| format!("component {component_id:?} missing")) } pub async fn component_definition( &mut self, component: ComponentId, ) -> anyhow::Result<ComponentDefinitionId> { let component_definition = match component { ComponentId::Root => ComponentDefinitionId::Root, ComponentId::Child(component_id) => { let component_doc_id = self.resolve_component_id(component_id)?; let component_doc: ParsedDocument<ComponentMetadata> = self .tx .get(component_doc_id) .await? .context("component missing")? .parse()?; ComponentDefinitionId::Child(component_doc.definition_id) }, }; Ok(component_definition) } pub async fn load_component( &mut self, id: ComponentId, ) -> anyhow::Result<Option<ParsedDocument<ComponentMetadata>>> { let result = match id { ComponentId::Root => self.root_component()?, ComponentId::Child(internal_id) => { let component_doc_id = self.resolve_component_id(internal_id)?; self.tx .get(component_doc_id) .await? .map(ParseDocument::parse) .transpose()? }, }; Ok(result) } pub async fn load_component_type(&mut self, id: ComponentId) -> anyhow::Result<ComponentType> { match self.load_component(id).await? { None => { if id.is_root() { // The root component's metadata document may be missing if the app hasn't been // updated to use components. Ok(ComponentType::App) } else { anyhow::bail!(ErrorMetadata::bad_request( "InvalidReference", format!("Component {id:?} not found"), )) } }, Some(component) => Ok(component.into_value().component_type), } } pub async fn load_component_args( &mut self, id: ComponentId, ) -> anyhow::Result<BTreeMap<Identifier, ConvexValue>> { let component = self .load_component(id) .await? .context(format!( "Component not found for ComponentId {:?}", id.serialize_to_string() ))? .into_value(); let args = match component.component_type { ComponentType::App => anyhow::bail!(ErrorMetadata::bad_request( "InvalidComponentType", "Can't load component args within the app", )), ComponentType::ChildComponent { args, .. } => args, }; let mut result = BTreeMap::new(); for (name, value) in args { let Resource::Value(value) = value else { anyhow::bail!("Non-value resource within component args"); }; result.insert(name, value); } Ok(result) } pub async fn load_definition( &mut self, id: ComponentDefinitionId, ) -> anyhow::Result<Option<ParsedDocument<ComponentDefinitionMetadata>>> { let internal_id = match id { ComponentDefinitionId::Root => match self.root_component()? { Some(root_component) => root_component.definition_id, None => return Ok(None), }, ComponentDefinitionId::Child(id) => id, }; let Some(doc) = self .tx .get_system::<ComponentDefinitionsTable>(TableNamespace::Global, internal_id) .await? else { return Ok(None); }; let mut doc = Arc::unwrap_or_clone(doc); if !doc.exports.is_empty() { metrics::log_nonempty_component_exports(); doc.exports = BTreeMap::new(); } Ok(Some(doc)) } pub async fn load_definition_metadata( &mut self, id: ComponentDefinitionId, ) -> anyhow::Result<ComponentDefinitionMetadata> { match self.load_definition(id).await? { Some(doc) => Ok(doc.into_value()), None => { if id.is_root() { // The root component's metadata document may be missing if the app hasn't been // updated to use components. Ok(ComponentDefinitionMetadata { path: ComponentDefinitionPath::root(), definition_type: ComponentDefinitionType::App, child_components: Vec::new(), exports: BTreeMap::new(), http_mounts: BTreeMap::new(), }) } else { anyhow::bail!(ErrorMetadata::bad_request( "InvalidReference", format!("Component definition {id:?} not found"), )) } }, } } #[fastrace::trace] pub async fn load_all_definitions( &mut self, ) -> anyhow::Result< BTreeMap<ComponentDefinitionPath, ParsedDocument<ComponentDefinitionMetadata>>, > { let mut query = ResolvedQuery::new( self.tx, TableNamespace::Global, Query::full_table_scan(COMPONENT_DEFINITIONS_TABLE.clone(), Order::Asc), )?; let mut definitions = BTreeMap::new(); while let Some(doc) = query.next(self.tx, None).await? { let mut definition: ParsedDocument<ComponentDefinitionMetadata> = doc.parse()?; if !definition.exports.is_empty() { metrics::log_nonempty_component_exports(); definition.exports = BTreeMap::new(); } anyhow::ensure!(definitions .insert(definition.path.clone(), definition) .is_none()); } Ok(definitions) } pub fn component_path_to_ids( &mut self, path: &ComponentPath, ) -> anyhow::Result<Option<(ComponentDefinitionId, ComponentId)>> { self.tx .component_registry .component_path_to_ids(path, &mut self.tx.reads) } pub fn must_component_path_to_ids( &mut self, path: &ComponentPath, ) -> anyhow::Result<(ComponentDefinitionId, ComponentId)> { self.component_path_to_ids(path)? .context("Component path not found") } pub fn function_path_to_module( &mut self, path: &CanonicalizedComponentFunctionPath, ) -> anyhow::Result<CanonicalizedComponentModulePath> { let (_, component) = self.must_component_path_to_ids(&path.component)?; Ok(CanonicalizedComponentModulePath { component, module_path: path.udf_path.module().clone(), }) } } #[cfg(test)] mod tests { use std::collections::BTreeMap; use common::{ bootstrap_model::components::{ definition::{ ComponentDefinitionMetadata, ComponentDefinitionType, ComponentInstantiation, }, ComponentMetadata, ComponentState, ComponentType, }, components::{ ComponentDefinitionPath, ComponentId, ComponentPath, }, }; use keybroker::Identity; use runtime::testing::TestRuntime; use super::definition::COMPONENT_DEFINITIONS_TABLE; use crate::{ bootstrap_model::components::{ BootstrapComponentsModel, COMPONENTS_TABLE, }, test_helpers::new_test_database, SystemMetadataModel, }; #[convex_macro::test_runtime] async fn test_component_path(rt: TestRuntime) -> anyhow::Result<()> { let db = new_test_database(rt.clone()).await; let mut tx = db.begin(Identity::system()).await?; let child_definition_path: ComponentDefinitionPath = "../app/child".parse().unwrap(); let child_definition_id = SystemMetadataModel::new_global(&mut tx) .insert( &COMPONENT_DEFINITIONS_TABLE, ComponentDefinitionMetadata { path: child_definition_path.clone(), definition_type: ComponentDefinitionType::ChildComponent { name: "child".parse().unwrap(), args: BTreeMap::new(), }, child_components: Vec::new(), http_mounts: BTreeMap::new(), exports: BTreeMap::new(), } .try_into()?, ) .await?; let root_definition_id = SystemMetadataModel::new_global(&mut tx) .insert( &COMPONENT_DEFINITIONS_TABLE, ComponentDefinitionMetadata { path: "".parse().unwrap(), definition_type: ComponentDefinitionType::App, child_components: vec![ComponentInstantiation { name: "child_subcomponent".parse().unwrap(), path: child_definition_path, args: Some(BTreeMap::new()), }], http_mounts: BTreeMap::new(), exports: BTreeMap::new(), } .try_into()?, ) .await?; let root_id = SystemMetadataModel::new_global(&mut tx) .insert( &COMPONENTS_TABLE, ComponentMetadata { definition_id: root_definition_id.into(), component_type: ComponentType::App, state: ComponentState::Active, } .try_into()?, ) .await?; let child_id = SystemMetadataModel::new_global(&mut tx) .insert( &COMPONENTS_TABLE, ComponentMetadata { definition_id: child_definition_id.into(), component_type: ComponentType::ChildComponent { parent: root_id.into(), name: "subcomponent_child".parse()?, args: Default::default(), }, state: ComponentState::Active, } .try_into()?, ) .await?; let resolved_path = BootstrapComponentsModel::new(&mut tx) .resolve_path(&ComponentPath::from(vec!["subcomponent_child".parse()?]))?; assert_eq!(resolved_path.unwrap().id(), child_id); let path = BootstrapComponentsModel::new(&mut tx) .must_component_path(ComponentId::Child(child_id.into()))?; assert_eq!( path, ComponentPath::from(vec!["subcomponent_child".parse()?]), ); 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