Skip to main content
Glama

Convex MCP server

Official
by get-convex
http.rs8.58 kB
use std::str::FromStr; use anyhow::Context; use common::sync::spsc; use deno_core::{ serde_v8, v8::{ self, }, }; use errors::ErrorMetadata; use headers::HeaderName; use serde::{ Deserialize, Serialize, }; use serde_json::{ json, Value as JsonValue, }; use url::{ form_urlencoded, Position, Url, }; use super::OpProvider; use crate::{ environment::{ helpers::with_argument_error, AsyncOpRequest, }, http::HttpRequestV8, request_scope::StreamListener, }; pub fn async_op_fetch<'b, P: OpProvider<'b>>( provider: &mut P, args: v8::FunctionCallbackArguments, resolver: v8::Global<v8::PromiseResolver>, ) -> anyhow::Result<()> { let arg: HttpRequestV8 = serde_v8::from_v8(provider.scope(), args.get(1))?; let request = with_argument_error("fetch", || HttpRequestV8::into_stream(arg, provider))?; let response_body_stream_id = provider.create_stream()?; provider.start_async_op( AsyncOpRequest::Fetch { request, response_body_stream_id, }, resolver, ) } pub fn async_op_parse_multi_part<'b, P: OpProvider<'b>>( provider: &mut P, args: v8::FunctionCallbackArguments, resolver: v8::Global<v8::PromiseResolver>, ) -> anyhow::Result<()> { let content_type: String = serde_v8::from_v8(provider.scope(), args.get(1))?; let request_stream_id: uuid::Uuid = serde_v8::from_v8(provider.scope(), args.get(2))?; let (request_sender, request_receiver) = spsc::unbounded_channel(); provider.new_stream_listener( request_stream_id, StreamListener::RustStream(request_sender), )?; provider.start_async_op( AsyncOpRequest::ParseMultiPart { content_type, request_stream: Box::pin(request_receiver.into_stream()), }, resolver, ) } #[convex_macro::v8_op] pub fn op_url_get_url_info<'b, P: OpProvider<'b>>( provider: &mut P, url: String, base: Option<String>, ) -> anyhow::Result<JsonValue> { let base_url = match base { Some(b) => match Url::parse(&b) { Ok(url) => Some(url), Err(_) => return Ok(json!({"kind": "error", "errorType": "InvalidURL"})), }, None => None, }; let parsed_url = match Url::options().base_url(base_url.as_ref()).parse(&url) { Ok(u) => u, // The URL spec (https://url.spec.whatwg.org/) dictates that JS // throw a TypeError when the URL is invalid, so we return `null` // and throw the error on the JS side instead of having this error // turn into a JsError Err(_) => return Ok(json!({"kind": "error", "errorType": "InvalidURL"})), }; match UrlInfo::try_from(parsed_url) { Ok(url_info) => Ok(json!({"kind": "success", "urlInfo": serde_json::to_value(url_info)?})), // This is a valid URL, but not one we support Err(e) => { Ok(json!({"kind": "error", "errorType": "UnsupportedURL", "message": e.to_string()})) }, } } #[convex_macro::v8_op] pub fn op_url_get_url_search_param_pairs<'b, P: OpProvider<'b>>( provider: &mut P, query_string: String, ) -> anyhow::Result<Vec<(String, String)>> { let parsed_url = form_urlencoded::parse(query_string.as_bytes()); let query_pairs: Vec<(String, String)> = parsed_url .into_iter() .map(|(key, value)| (key.into(), value.into())) .collect(); Ok(query_pairs) } #[convex_macro::v8_op] pub fn op_url_stringify_url_search_params<'b, P: OpProvider<'b>>( provider: &mut P, query_pairs: Vec<(String, String)>, ) -> anyhow::Result<String> { let search = form_urlencoded::Serializer::new(String::new()) .extend_pairs(query_pairs) .finish(); Ok(search) } #[convex_macro::v8_op] pub fn op_url_update_url_info<'b, P: OpProvider<'b>>( provider: &mut P, original_url: String, update: JsonValue, ) -> anyhow::Result<JsonValue> { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] enum Update { Hash { value: Option<String> }, Hostname { value: Option<String> }, Href { value: String }, Protocol { value: String }, Port { value: Option<String> }, Pathname { value: String }, Search { value: Option<String> }, SearchParams { value: Vec<(String, String)> }, } let update: Update = serde_json::from_value(update)?; let mut parsed_url = Url::parse(&original_url)?; match update { Update::Hash { value } => parsed_url.set_fragment(value.as_deref()), Update::SearchParams { value } => { if value.is_empty() { parsed_url.set_query(None) } else { parsed_url .query_pairs_mut() .clear() .extend_pairs(value) .finish(); } }, Update::Hostname { value } => parsed_url.set_host(value.as_deref())?, Update::Href { value } => { parsed_url = Url::parse(&value).context(ErrorMetadata::bad_request( "BadUrl", format!("Could not parse URL: {original_url}"), ))?; }, Update::Protocol { value } => { if value != "http" && value != "https" { parsed_url .set_scheme(&value) .map_err(|_e| anyhow::anyhow!("Failed to set scheme"))? } }, Update::Port { value } => match value { Some(port_str) => match port_str.parse::<u16>() { Ok(port_number) => parsed_url .set_port(Some(port_number)) .map_err(|_e| anyhow::anyhow!("Failed to set port"))?, Err(_) => (), }, None => parsed_url .set_port(None) .map_err(|_e| anyhow::anyhow!("Failed to set port"))?, }, Update::Pathname { value } => parsed_url.set_path(&value), Update::Search { value } => parsed_url.set_query(value.as_deref()), } let url_info: UrlInfo = parsed_url.try_into()?; Ok(serde_json::to_value(url_info)?) } #[convex_macro::v8_op] pub fn op_headers_get_mime_type<'b, P: OpProvider<'b>>( provider: &mut P, content_type: String, ) -> anyhow::Result<Option<MimeType>> { let mime_type = match mime::Mime::from_str(&content_type) { Ok(mime_type) => mime_type, Err(_) => { // Invalid mime type, so turn it into null on the JS side. return Ok(None); }, }; Ok(Some(MimeType { essence: mime_type.essence_str().to_string(), boundary: mime_type.get_param(mime::BOUNDARY).map(|b| b.to_string()), })) } #[convex_macro::v8_op] pub fn op_headers_normalize_name<'b, P: OpProvider<'b>>( provider: &mut P, name: String, ) -> anyhow::Result<Option<String>> { let result = match HeaderName::from_bytes(name.as_bytes()) { Ok(normalized_name) => Some(normalized_name.as_str().into()), // This is an invalid header name, so turn it into a TypeError on the // JS side Err(_) => None, }; Ok(result) } #[derive(Deserialize, Serialize)] struct UrlInfo { scheme: String, hash: String, host: String, hostname: String, href: String, pathname: String, port: String, search: String, username: String, password: String, } impl TryFrom<Url> for UrlInfo { type Error = anyhow::Error; fn try_from(value: Url) -> Result<Self, Self::Error> { let url_info = UrlInfo { scheme: value.scheme().to_string(), hash: value[Position::BeforeFragment..Position::AfterFragment].to_string(), host: value[Position::BeforeHost..Position::BeforePath].to_string(), hostname: value[Position::BeforeHost..Position::AfterHost].to_string(), href: value.to_string(), pathname: value[Position::BeforePath..Position::AfterPath].to_string(), port: value[Position::BeforePort..Position::AfterPort].to_string(), search: value[Position::BeforeQuery..Position::AfterQuery].to_string(), username: value.username().to_owned(), password: value.password().unwrap_or_default().to_owned(), }; Ok(url_info) } } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] struct MimeType { essence: String, boundary: Option<String>, }

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