Recall Select

recall.select
Un sistema de memoria agéntico mínimo: proporciona una URL a cualquier agente y este obtiene memoria a largo plazo con una configuración casi nula. Construido sobre Qdrant + FastMCP + FastAPI/Bootstrap.
Consulta docs/specs/initial_specification.md para el diseño completo y el plan de construcción incremental, y docs/specs/changelog.md para un registro continuo de cambios notables.
Cómo funciona
La memoria se almacena como vectores. Cada almacén de memoria es una colección de Qdrant, mapeada uno a uno a un par (usuario, proyecto). Los metadatos alrededor de esos vectores (usuarios, claves API, proyectos y estadísticas de uso/límite por colección) residen en MongoDB.
flowchart LR
agent[Agent] --> web[FastAPI / MCP]
web <-->qdrant[Qdrant]
web <--> mongo[MongoDB]
web <--> embed[Embedding API]Las colecciones de Qdrant se crean de forma diferida: nada toca Qdrant hasta que la primera memoria se almacena en un par (usuario, proyecto).
Related MCP server: LedgerMem MCP Server
Arquitectura
app/main.py- Aplicación FastAPI. Sirve la página de inicio Bootstrap y, al iniciarse, garantiza que los índices de Mongo existan (tolerante a una base de datos fría/remota).app/mcp_server.py- El servidor MCP detrás del enlace de memoria. El cliente MCP de un agente apunta a{PUBLIC_BASE_URL}/m/{key}(HTTP Streamable, sin estado, respuestas JSON); la clave API en la ruta es la credencial completa y limita las herramientas al proyecto predeterminado del propietario de la clave. Herramientas básicas:store_memory/recall_memory/delete_memory. Herramientas de capa semántica (vervector_semantics.py):link_memories/unlink_memories/annotate_memory/memory_connections/recall_connected- el agente conectado realiza el razonamiento de relaciones del lado del cliente (solo bajo demanda explícita) y estas ingieren o recorren el resultado. La misma clave también se puede enviar comoAuthorization: Bearercontra el endpoint/mcpsin clave, para mantener el secreto fuera de la URL/logs.{...}/m/{key}.md(enapp/api/connect.py) sirve las instrucciones de configuración correspondientes (ambas formas).app/dependencies.py- El contenedor DI central (injector). Construye los singletons compartidos (cliente Qdrant, cliente/BD Mongo, el incrustador remoto). Las dependencias de FastAPI (app/api/deps.py) y el inicio se resuelven desdeapp_containeren lugar de construir los clientes ellos mismos.app/services/- La capa de servicios (sin código HTTP/ruta, solo E/S):qdrant_store.py- Cliente Qdrant +ensure_collection/upsert_memory/search/delete_memory, más las primitivas a nivel de punto que necesita la capa semántica (neighbors,scroll_points,retrieve_points,set_payload).vector_semantics.py- La capa de utilidad de memoria vectorial: trata un almacén como un grafo de significado. Un espacio de nombres reservado_semanticsen la carga útil de cada punto contiene anclajes deícticos (propietario, almacenado en; escritos en el momento del almacenamiento), entidades extraídas por el cliente y relaciones tipadas declaradas por el cliente (upsert_relationslas valida y almacena - sin llamadas LLM del lado del servidor). Las relaciones declaradas llevan dos indicadores de calidad:confidence(0-1], escala la fuerza de recorrido del borde) yvalid_till(ISO 8601; los bordes caducados son ignorados por todas las rutas de lectura, por lo que la estructura obsoleta se retira sola). Higiene:remove_relationselimina bordes incorrectos (el gemelo correctivo deupsert_relations), ymemory.delete_memoryllama aprune_relations_topara que ningún borde suelto sobreviva a la eliminación de una memoria. Lentes conectables (topical/temporal/entity/declared) derivan bordes tipados; sobre ellos se sitúansemantic_graph(multigrafo),spreading_activation(recuperación por conexión),concept_clusters(ontología emergente) einfer_relation(verdad declarada primero, heurísticas geométricas después). Nota de rendimiento: la búsqueda de bordes entrantes (relations_of(include_incoming=True)) es hoy un desplazamiento y escaneo acotado. Si el recorrido inverso se vuelve intensivo, la solución es un índice de carga útil de Qdrant en_semantics.relations[].target(create_payload_index, esquema de palabra clave) y una consulta filtrada en lugar del escaneo - el mismo almacén, solo un índice; nada en el esquema cambia.mongo.py- Cliente Mongo,get_db()yensure_indexes()(aplica la regla uno a uno(usuario, proyecto)con un índice compuesto único).users.py-add_user,get_user,get_user_by_email,update_user.api_keys.py- Claves limitadas por usuario, almacenadas como un hash SHA-256 (el texto plano se devuelve una vez, desdeadd_api_key, y nunca se persiste):add_api_key,delete_api_key,delete_user_keys,list_api_keys,get_labeled_key,get_by_key(hashea el token presentado y coincide con el resumen;record_use=Trueen la puerta de autenticación MCP marcalast_used_at). En reposo, cada clave también mantiene sugerencias de visualización no secretas -key_prefix+key_last4, renderizadas pormasked()comors_ab12…wxyz- para que las claves puedan listarse y distinguirse sin reexponer el secreto.projects.py-add_project,get_project,list_projects,update_project,delete_project.collections.py- El registro(usuario, proyecto) ↔ colección Qdrant.collection_name(user_id, project_id)es el estándar de nomenclatura interna (rs_{user}_{project}); rastreapoints_count/calls_countpara límites y estadísticas.collection_provisioning.py- El paso de dos carascreate_collection/destroy_collection. Una colección solo existe una vez que tanto su fila de registro en Mongo como su colección Qdrant de respaldo existen; esto compone el registrocollectionsconqdrant_storeen una operación atómica e idempotente para que los dos almacenes nunca se desincronicen. La creación es diferida, por lo que su único llamante creador es la primera escritura de memoria (memory.store_memory); la eliminación de la API de colecciones usadestroy_collection.embeddings.py- La abstracciónEmbedder;embeddings_remote.py- el backend concreto texto→vector (API de incrustación remota, p. ej., DeepInfra).monobank.py- Cliente de adquisición mínimo de Monobank (create_invoice,fetch_invoice_status) más autenticación de webhook (fetch_pubkey/verify_signature, ECDSA-SHA256 sobre el cuerpo sin procesar). Reutiliza el token de comerciante de mcp-api.net; recall.select posee su propia factura/redirección/webhook.billing.py- El catálogo de planes y el registro de pago claveado porinvoiceIdde Monobank.record_pendingal finalizar la compra;apply_webhookcambia eltierdel comprador una vez ensuccess(idempotente contra reintentos/duplicados);reconcileresuelve lo que el webhook perdió (abajo). Un nivel tiene límite de tiempo:grant_tieres el único lugar donde se otorga el derecho (factura pagada o buena voluntad del propietario), escribiendotier_expires_atmás una fila de auditoría entier_grants;effective_tier(user)es lo que toda verificación debe leer, ya que unpaid_2xalmacenado cuya fecha ha pasado es una cuenta gratuita. También es la fuente única de verdad para las asignaciones por nivel:call_allowance(tier)/project_allowance(tier)(None= ilimitado; niveles desconocidos caen en el gratuito).usage.py- El medidor de llamadas mensual y la puerta del modelo de precios. Cada store/recall/delete aceptado se contabiliza en una filausagepor(usuario, mes-calendario);check_call_allowedrechaza una llamada una vez que se ha gastado lacall_allowancemensual del nivel, lanzandoQuotaExceeded. Se aplica enmemory.py(por lo que tanto las herramientas MCP como la API de memoria HTTP están cubiertas) y se mapea a HTTP 429 porapp/main.py; el transporte MCP lo muestra como un error de herramienta. Separado delcalls_countdecollectionsde todos los tiempos.account.py- La instantánea de solo lectura que muestra la página/accountcon sesión iniciada (plan, uso mensual, recuentos almacenados por proyecto y la lista de claves API en forma enmascarada con fechas de creación/último uso), compuesta a partir debilling/usage/projects/collections/api_keys.docs.py- Contenido para las guías de integración públicas/docs. Construye la configuración del cliente MCP en un solo lugar (mcp_config/mcp_config_json), reutilizado tanto por las páginas de documentación como por el.mdpor clave deapp/api/connect.py, para que los dos nunca diverjan.INTEGRATIONSes el registro de guías (agrega una página añadiendo una entrada).
Páginas públicas (servidas desde app/main.py, Bootstrap + Jinja, i18n a través de app/translations/*.yml): / página de inicio, /plans, /account (con sesión iniciada) y las guías /docs/integrations. La documentación de API incorporada de FastAPI se mueve de /docs a /api/docs (/api/redoc, /api/openapi.json) para que el sitio público sea propietario de /docs.
Los pagos se manejan en la capa HTTP en app/api/payments.py: POST /api/me/checkout (con sesión iniciada) crea la factura y devuelve el pay_url de Monobank; el POST /webhooks/monobank verificado otorga el nivel; GET /payment/success|fail son las páginas de retorno del navegador cosméticas (el derecho se basa en el webhook, nunca en estas).
El derecho no depende solo del webhook. Monobank envía cada cambio de estado una vez y nunca lo reenvía, por lo que una devolución de llamada perdida por un reinicio o un problema de proxy dejaría a un cliente que pagó en su nivel anterior, sin que nada de nuestro lado lo note. Por lo tanto, la aplicación también extrae: cada PAYMENT_RECONCILE_MINUTES un barrido en segundo plano (billing.reconcile_with_monobank, iniciado en el ciclo de vida de app/main.py) obtiene el estado real de cada pago aún en vuelo después de cinco minutos y lo empuja a través de la misma transición apply_webhook. Push y pull son idempotentes entre sí: el que llegue primero otorga el nivel, el otro es un no-op. Se escribe una fila en payments cuando comienza el proceso de pago, por lo que created significa "abrió la página de pago", no "pagó"; /admin/payments muestra esa distinción explícitamente.
Una compra compra un mes (SUBSCRIPTION_DAYS), no para siempre: grant_tier marca tier_expires_at, el mismo bucle en segundo plano devuelve las cuentas vencidas a gratuitas (downgrade_expired), y la página de cuenta muestra la fecha en que el plan se agota. Nada se renueva automáticamente todavía: el usuario compra de nuevo, y comprar mientras aún tiene crédito extiende la ventana en lugar de reiniciarla. El derecho se lee a través de effective_tier, por lo que una concesión vencida deja de pagar inmediatamente incluso antes de que el barrido reescriba el campo almacenado.
Debido a que nada se renueva por sí solo, la aplicación pregunta: billing.renewal_state(user) impulsa un aviso en /account: una advertencia con un botón de Renovar con un solo clic en los últimos RENEWAL_WARNING_DAYS (7) de un plan, y un aviso de "tu plan terminó, renueva" durante LAPSED_PROMPT_DAYS (30) después (el barrido de degradación registra lapsed_tier / tier_lapsed_at para que la página aún pueda decir qué se agotó). Renovar publica en el mismo /api/me/checkout que usa la página de planes, preestablecido al plan que tenían. No hay correo electrónico todavía: el aviso solo llega a los usuarios que visitan.
Cada función CRUD toma un argumento opcional db=/client= para que pueda ser impulsada en pruebas sin un backend en vivo.
Configuración
Se configura a través del entorno (un .env local se carga automáticamente; nunca lo confirmes - ver .env.example):
Variable | Default | Propósito |
| (obligatorio) | String de conexión remota a MongoDB gestionada. |
|
| Nombre de la base de datos. |
|
| Endpoint de Qdrant (red interna de compose). |
| (ninguno local; obligatorio en producción) | Secreto compartido entre la aplicación y Qdrant. Compose establece |
|
| Dimensión del vector para cada colección. Se solicita al embedder remoto (mediante el parámetro |
| (obligatorio) | Clave API para la API de embeddings remota. |
|
| URL base de la API de embeddings compatible con OpenAI. |
| (obligatorio para iniciar sesión) | ID de cliente web de Google OAuth 2.0. |
| (obligatorio para iniciar sesión) | Secreto de cliente de Google OAuth 2.0. |
| (fallback de desarrollo) | Firma la cookie de sesión. Establece un valor estable en producción. |
|
| Origen público; construye el enlace de memoria + la URI de redirección de OAuth. |
|
| Pares de los que uvicorn confía en |
| (obligatorio para pagos) | Token de comercio de adquisición de Monobank. Compartido con la plataforma mcp-api.net - mismo comercio, una cuenta; las facturas se distinguen por |
|
| Donde vuelve el navegador del comprador después de pagar. |
|
| Callback de servidor a servidor que concede el nivel. Debe ser accesible públicamente. |
|
| Verificar la |
|
| Cada cuánto tiempo se obtiene el estado real de los pagos en curso desde Monobank, para que un webhook perdido no deje a un cliente de pago varado. |
| (sin establecer - área deshabilitada) | Desbloquea el área de administración del propietario en |
|
| Cuánto dura una sesión de administración desbloqueada antes de volverse a bloquear. |
Área de administración del propietario (/admin)
Una ventana de solo lectura al área personal de cualquier usuario, para soporte y para ver
lo que ve un usuario. Establece ADMIN_SECRET (genera:
python -c "import secrets; print(secrets.token_urlsafe(32))"), recrea el
contenedor web, luego abre {PUBLIC_BASE_URL}/admin e ingresa la clave una vez por
sesión. /admin/users lista todas las cuentas -buscables por correo, nombre o ID de
usuario- y cada fila abre el plan de ese usuario, el uso de este período, los proyectos con sus
recuentos de memoria y los enlaces de memoria en forma enmascarada.
Los límites son deliberados: la clave se envía mediante POST (nunca un parámetro
de URL, por lo que no queda en el historial ni en los registros de acceso), los intentos incorrectos repetidos
bloquean a un cliente durante cinco minutos, la sesión se vuelve a bloquear después de
ADMIN_SESSION_HOURS, y ninguna ruta aquí escribe nada ni revela texto de memoria
o secretos clave - el propietario ve la forma de una cuenta, no su contenido.
Con ADMIN_SECRET sin establecer, el área no existe en absoluto.
Autenticación (inicio de sesión con Google)
El inicio de sesión controla el enlace de memoria: un usuario inicia sesión con Google, luego hace clic en
Copiar enlace de memoria para aprovisionar su proyecto predeterminado + colección + clave API y obtener
la URL para alimentar a un agente. El secreto se muestra exactamente una vez (solo se almacena su hash):
después, la página de inicio muestra el enlace enmascarado (a través de
GET /api/me/link) y el botón se convierte en un explícito y confirmado
"obtener un nuevo enlace": la regeneración invalida el enlace anterior, nunca de forma silenciosa.
Las claves se gestionan en /account: lista enmascarada, fechas de creación/último uso,
crear con etiqueta (revelar una vez) y revocar. Para configurar las credenciales de Google:
Consola de Google Cloud → APIs y servicios → Pantalla de consentimiento de OAuth - configúrala (Externa; agrega tu correo como usuario de prueba mientras no esté verificado).
Credenciales → Crear credenciales → ID de cliente de OAuth → Aplicación web.
Agrega una URI de redirección autorizada:
{PUBLIC_BASE_URL}/auth/callback- por ejemplo,http://localhost:8000/auth/callbackpara desarrollo local yhttps://recall.select/auth/callbacken producción (agrega ambas si pruebas localmente).Copia el ID de cliente y el Secreto de cliente en
.env(GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET), y establece unSESSION_SECRETestable (python -c "import secrets; print(secrets.token_urlsafe(48))").
Ejecutar localmente
El stack completo (web + Qdrant) mediante Docker Compose:
cp .env.example .env # then fill in MONGODB_URI
docker compose up --build
# open http://localhost:8000O solo la aplicación, contra tu propio Qdrant/Mongo:
pip install -e ".[dev]"
uvicorn app.main:app --reloadPruebas
pip install -e ".[dev]"
pytestLas pruebas CRUD se ejecutan contra una Mongo en memoria (mongomock) y los clientes de Qdrant/embedding
son simulados; no se requieren backends en vivo.
Desplegar
./deploy/deploy.shEl mismo comando funciona desde dos lugares: detecta dónde se ejecuta:
Desde una máquina de desarrollo (o la caja del agente): envía confirmaciones locales, luego ejecuta el despliegue en el servidor a través del alias SSH
recall-server.En el propio servidor (
setti@setti-server:~/recall_select$ ./deploy/deploy.sh): despliega in situ, sin salto SSH.
Ambas rutas ejecutan el mismo trabajador - deploy/_server_deploy.sh:
sincronización git de master, reconstrucción del stack de Compose (FastAPI web + Qdrant), recarga
del proxy Caddy compartido (HTTPS automático para recall.select), limpieza de imágenes antiguas.
MongoDB es remoto/gestionado, por lo que la autenticación/MONGODB_URI env (ver .env) debe estar presente
en el servidor.
Quien lo ejecute en el servidor necesita acceso de extracción de GitHub al repositorio (una
clave SSH autorizada en su ~/.ssh) y pertenencia al grupo docker - ambas
son ciertas para claude-agent y setti. El trabajador registra automáticamente el repositorio como un safe.directory de git
para que un desplegador que no sea el propietario del repositorio no se vea bloqueado por
"propiedad dudosa".
Despliegues automatizados (CI)
Cada push a master se despliega automáticamente mediante GitHub Actions
(.github/workflows/deploy.yml) - el mismo flujo que
arriba, solo que activado por CI en lugar de una persona. El trabajo se conecta por SSH al servidor y
canaliza deploy/_server_deploy.sh a través de stdin, por lo que ejecuta
la lógica de despliegue de la confirmación enviada. Los despliegues están serializados (concurrency), y un
botón Run workflow (workflow_dispatch) te permite desplegar bajo demanda.
Configuración única: agrega en Settings → Secrets and variables → Actions:
Secreto | Obligatorio | Propósito |
| sí | Clave privada cuya mitad pública está en el |
| sí | Dirección del servidor y el usuario SSH para realizar el despliegue. |
| no | Puerto SSH (por defecto |
| no | Fijar la clave de host del servidor; si no se establece, CI confía en él en el primer uso mediante |
Los secretos de la aplicación (MONGODB_URI, OAuth, etc.) permanecen en el .env del servidor - CI nunca
los ve.
Licencia
Licenciado bajo la GNU Affero General Public License v3.0. Si ejecuta una versión modificada como servicio de red, la AGPL requiere que ofrezca su código fuente a sus usuarios. Copyright © 2026 Sergii Setti.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenancePersistent memory for AI agents. Store, recall, and share knowledge across sessions with five MCP tools: remember, recall, context, forget, and share. Includes semantic search and agent/user/org scoping.52Apache 2.0

LedgerMem MCP Serverofficial
AlicenseNot gradedqualityBmaintenanceEnables persistent memory storage and retrieval for MCP clients, allowing AI assistants to remember facts and context across conversations.10MIT- AlicenseNot gradedqualityCmaintenanceProvides persistent, local-first AI memory across sessions via MCP tools for storing, searching, and retrieving context from past interactions.1MIT
- AlicenseAqualityAmaintenanceProvides persistent memory for AI agents via 10 MCP tools that map to the AgentRAM REST API, enabling store, retrieve, search, and share memories across personal and shared namespaces.10191MIT
Related MCP Connectors
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
Shared long-term memory vault for AI agents with 20 MCP tools.
Your memory, everywhere AI goes. Build knowledge once, access it via MCP anywhere.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/SergeySetti/recall_select'
If you have feedback or need assistance with the MCP directory API, please join our Discord server