Recall Select

recall.select
Минималистичная агентная система памяти — передайте один URL любому агенту, и он получит долговременную память с почти нулевой настройкой. Построена на Qdrant + FastMCP + FastAPI/Bootstrap.
Полное описание дизайна и поэтапный план сборки см. в docs/specs/initial_specification.md, а историю заметных изменений — в docs/specs/changelog.md.
Как это работает
Память хранится в виде векторов. Каждое хранилище памяти — это коллекция Qdrant, сопоставленная один к одному с парой (user, project). Метаданные вокруг этих векторов — пользователи, API-ключи, проекты и статистика использования/лимитов по коллекциям — хранятся в MongoDB.
flowchart LR
agent[Agent] --> web[FastAPI / MCP]
web <-->qdrant[Qdrant]
web <--> mongo[MongoDB]
web <--> embed[Embedding API]Коллекции Qdrant создаются лениво: ничего не обращается к Qdrant, пока первое воспоминание не будет сохранено в пару (user, project).
Related MCP server: LedgerMem MCP Server
Архитектура
app/main.py— приложение FastAPI. Обслуживает Bootstrap-лендинг и при запуске проверяет существование индексов Mongo (устойчиво к холодной/удаленной БД).app/mcp_server.py— MCP-сервер, стоящий за ссылкой на память. MCP-клиент агента указывает на{PUBLIC_BASE_URL}/m/{key}(Streamable HTTP, без состояния, JSON-ответы); API-ключ в пути является единственным учетным данным и ограничивает инструменты проектом по умолчанию владельца ключа. Базовые инструменты:store_memory/recall_memory/delete_memory. Инструменты семантического слоя (см.vector_semantics.py):link_memories/unlink_memories/annotate_memory/memory_connections/recall_connected— подключенный агент выполняет рассуждения о связях на стороне клиента (только по явному запросу), а эти инструменты принимают или обходят результат. Тот же ключ можно отправить какAuthorization: Bearerк конечной точке/mcpбез ключа, чтобы скрыть секрет из URL/логов.{...}/m/{key}.md(вapp/api/connect.py) обслуживает соответствующие инструкции по настройке (обе формы).app/dependencies.py— основной DI-контейнер (injector). Создает общие синглтоны (клиент Qdrant, клиент/БД Mongo, удаленный эмбеддер). Зависимости FastAPI (app/api/deps.py) и код запуска разрешаются изapp_container, а не создают клиенты самостоятельно.app/services/— сервисный слой (без HTTP/кода маршрутов, только ввод-вывод):qdrant_store.py— клиент Qdrant +ensure_collection/upsert_memory/search/delete_memory, а также примитивы уровня точек, необходимые семантическому слою (neighbors,scroll_points,retrieve_points,set_payload).vector_semantics.py— слой утилит векторной памяти: рассматривает хранилище как граф смыслов. Зарезервированное пространство имен_semanticsв полезной нагрузке каждой точки содержит дейктические якоря (владелец, время сохранения; записываются при сохранении), извлеченные клиентом сущности и объявленные клиентом типизированные отношения (upsert_relationsпроверяет и сохраняет их — никаких LLM-вызовов на стороне сервера). Объявленные отношения имеют две качественные оговорки:confidence(0-1], масштабирует силу обхода ребра) иvalid_till(ISO 8601; просроченные ребра игнорируются всеми путями чтения, поэтому устаревшая структура самоуничтожается). Гигиена:remove_relationsудаляет неверные ребра (корректирующий двойникupsert_relations), аmemory.delete_memoryвызываетprune_relations_to, чтобы после удаления памяти не осталось висящих ребер. Подключаемые линзы (topical/temporal/entity/declared) выводят типизированные ребра; поверх них работаютsemantic_graph(мультиграф),spreading_activation(извлечение по связям),concept_clusters(эмерджентная онтология) иinfer_relation(сначала объявленная истина, затем геометрические эвристики). Заметка о производительности: поиск входящих ребер (relations_of(include_incoming=True)) сегодня представляет собой ограниченный scroll-and-scan. Если обратный обход станет горячим путем, исправлением будет индекс полезной нагрузки Qdrant на_semantics.relations[].target(create_payload_index, схема ключевого слова) и фильтрованный запрос вместо сканирования — то же хранилище, только индекс; ничего в схеме не меняется.mongo.py— клиент Mongo,get_db()иensure_indexes()(обеспечивает правило «один к одному»(user, project)с помощью уникального составного индекса).users.py—add_user,get_user,get_user_by_email,update_user.api_keys.py— ключи, привязанные к пользователю, хранятся в виде хеша SHA-256 (открытый текст возвращается один раз, изadd_api_key, и никогда не сохраняется):add_api_key,delete_api_key,delete_user_keys,list_api_keys,get_labeled_key,get_by_key(хеширует представленный токен и сопоставляет с дайджестом;record_use=Trueна шлюзе аутентификации MCP штампуетlast_used_at). В состоянии покоя каждый ключ также хранит несекретные подсказки для отображения —key_prefix+key_last4, отображаемыеmasked()какrs_ab12…wxyz— чтобы ключи можно было перечислить и различить, не раскрывая секрет повторно.projects.py—add_project,get_project,list_projects,update_project,delete_project.collections.py— реестр соответствия(user, project) ↔ коллекция Qdrant.collection_name(user_id, project_id)— внутренний стандарт именования (rs_{user}_{project}); отслеживаетpoints_count/calls_countдля лимитов и статистики.collection_provisioning.py— двухсторонний шагcreate_collection/destroy_collection. Коллекция существует только тогда, когда существуют и ее строка в реестре Mongo, и ее резервная коллекция Qdrant; это объединяет реестрcollectionsсqdrant_storeв одну атомарную, идемпотентную операцию, чтобы два хранилища никогда не рассинхронизировались. Создание лениво, поэтому его единственный вызывающий — первая запись памяти (memory.store_memory); API коллекции для удаления используетdestroy_collection.embeddings.py— абстракцияEmbedder;embeddings_remote.py— конкретный бэкенд текст→вектор (удаленное API эмбеддингов, например DeepInfra).monobank.py— минимальный клиент эквайринга Monobank (create_invoice,fetch_invoice_status) плюс аутентификация вебхука (fetch_pubkey/verify_signature, ECDSA-SHA256 по сырому телу). Использует тот же мерчант-токен mcp-api.net; recall.select владеет собственными счетом/редиректом/вебхуком.billing.py— каталог тарифов и запись об оплате, привязанная кinvoiceIdMonobank.record_pendingпри оформлении заказа;apply_webhookпереключаетtierпокупателя один раз приsuccess(идемпотентно к повторным попыткам/дубликатам);reconcileобрабатывает то, что пропустил вебхук (см. ниже). Тариф ограничен по времени:grant_tier— единственное место, где когда-либо выдается право (оплаченный счет или добрая воля владельца), записываяtier_expires_atплюс строку аудита вtier_grants;effective_tier(user)— то, что должна читать каждая проверка, так как сохраненныйpaid_2x, срок которого истек, означает бесплатную учетную запись. Также единственный источник истины для разрешений по тарифам:call_allowance(tier)/project_allowance(tier)(None= безлимитно; неизвестные тарифы возвращаются к бесплатному).usage.py— ежемесячный счетчик вызовов и шлюз ценовой модели. Каждый принятый store/recall/delete учитывается в строкеusageдля(user, календарный-месяц);check_call_allowedотклоняет вызов, когда израсходован месячныйcall_allowanceтарифа, вызываяQuotaExceeded. Применяется вmemory.py(так что охвачены как MCP-инструменты, так и HTTP API памяти) и преобразуется в HTTP 429 вapp/main.py; транспорт MCP отображает это как ошибку инструмента. Отдельно от общегоcollections.calls_count.account.py— доступный только для чтения снимок, который показывает страница/accountдля вошедшего пользователя (тариф, ежемесячное использование, количество сохраненных элементов по проектам и список API-ключей в замаскированной форме с датами создания/последнего использования), составленный изbilling/usage/projects/collections/api_keys.docs.py— контент для публичных руководств по интеграции/docs. Собирает конфигурацию MCP-клиента в одном месте (mcp_config/mcp_config_json), повторно используемую как страницами документации, так иapp/api/connect.pyдля.mdпо ключу, так что они никогда не расходятся.INTEGRATIONS— реестр руководств (добавьте страницу, добавив запись).
Публичные страницы (обслуживаются из app/main.py, Bootstrap + Jinja, i18n через app/translations/*.yml): лендинг /, /plans, /account (для вошедших) и руководства /docs/integrations. Встроенная документация API FastAPI перемещена с /docs на /api/docs (/api/redoc, /api/openapi.json), чтобы публичный сайт владел /docs.
Платежи работают через HTTP-слой в app/api/payments.py: POST /api/me/checkout (для вошедших) создает счет и возвращает pay_url Monobank; проверенный POST /webhooks/monobank предоставляет тариф; GET /payment/success|fail — это косметические страницы возврата в браузере (право предоставляется через вебхук, никогда через них).
Право не зависит только от вебхука. Monobank отправляет каждое изменение статуса один раз и никогда не пересылает его, поэтому обратный вызов, потерянный из-за перезапуска или сбоя прокси, оставил бы платящего клиента на его старом тарифе, и на нашей стороне не было бы механизма это заметить. Поэтому приложение также выполняет опрос: каждые PAYMENT_RECONCILE_MINUTES фоновый обход (billing.reconcile_with_monobank, запускаемый в жизненном цикле app/main.py) получает реальный статус каждого платежа, все еще находящегося в обработке после пяти минут, и пропускает его через тот же переход apply_webhook. Push и pull идемпотентны друг к другу — какой бы ни сработал первым, предоставляет тариф, другой является no-op. Строка в payments записывается, когда оформление начинается, поэтому created означает «открыл страницу оплаты», а не «оплатил»; /admin/payments показывает это различие явно.
Покупка дает один месяц (SUBSCRIPTION_DAYS), не навсегда: grant_tier штампует tier_expires_at, тот же фоновый цикл возвращает просроченные учетные записи на бесплатный тариф (downgrade_expired), а страница учетной записи показывает дату окончания тарифа. Ничто не продлевается автоматически — пользователь покупает снова, и покупка, пока еще есть кредит, продлевает окно, а не перезапускает его. Право читается через effective_tier, поэтому истекший грант перестает действовать немедленно, даже до того, как обход перезапишет сохраненное поле.
Поскольку ничто не продлевается само по себе, приложение спрашивает: billing.renewal_state(user) отображает подсказку на /account — предупреждение с кнопкой «Продлить» в один клик в последние RENEWAL_WARNING_DAYS (7) действия тарифа и подсказку «ваш тариф закончился, продлите его» в течение LAPSED_PROMPT_DAYS (30) после этого (обход понижения записывает lapsed_tier / tier_lapsed_at, чтобы страница все еще могла показать, что закончилось). Продление отправляет POST на тот же /api/me/checkout, который использует страница тарифов, с предустановленным тарифом, который был у пользователя. Пока нет email — подсказка доходит только до пользователей, посещающих сайт.
Каждая CRUD-функция принимает необязательный аргумент db=/client=, чтобы ее можно было использовать в тестах без работающего бэкенда.
Конфигурация
Задается через окружение (локальный .env загружается автоматически; никогда не коммитьте его — см. .env.example):
Переменная | По умолчанию | Назначение |
| (обязательно) | Строка подключения к удаленной управляемой MongoDB. |
|
| Имя базы данных. |
|
| Конечная точка Qdrant (внутренняя сеть Compose). |
| (нет локально; обязательно в продакшене) | Общий секрет между приложением и Qdrant. Compose устанавливает |
|
| Размерность вектора для каждой коллекции. У удаленного эмбеддера запрашивается (через параметр API |
| (обязательно) | Ключ API для удаленного API эмбеддинга. |
|
| Базовый URL API эмбеддингов, совместимого с OpenAI. |
| (обязательно для входа) | Идентификатор веб-клиента Google OAuth 2.0. |
| (обязательно для входа) | Секрет клиента Google OAuth 2.0. |
| (запасной вариант для разработки) | Подписывает cookie сессии. Установите стабильное значение в продакшене. |
|
| Публичный источник; формирует ссылку на память + URI перенаправления OAuth. |
|
| Пиры, чьим |
| (обязательно для платежей) | Токен мерчанта Monobank. Общий с платформой mcp-api.net - один мерчант, один аккаунт; счета различаются по |
|
| Куда браузер покупателя возвращается после оплаты. |
|
| Серверный обратный вызов, который предоставляет уровень доступа. Должен быть общедоступным. |
|
| Проверять |
|
| Как часто запрашивать реальный статус текущих платежей у Monobank, чтобы потерянный вебхук не оставил платящего клиента в подвешенном состоянии. |
| (не задан - область отключена) | Разблокирует область администратора владельца по адресу |
|
| Как долго длится разблокированная сессия администратора, прежде чем она снова заблокируется. |
Область администратора владельца (/admin)
Окно только для чтения в личную область любого пользователя для поддержки и просмотра того, что видит пользователь. Установите ADMIN_SECRET (сгенерировать: python -c "import secrets; print(secrets.token_urlsafe(32))"), пересоздайте веб-контейнер, затем откройте {PUBLIC_BASE_URL}/admin и введите ключ один раз за сессию. /admin/users выводит список всех учетных записей - доступен поиск по email, имени или ID пользователя - и каждая строка открывает тарифный план этого пользователя, использование за текущий период, проекты с количеством записей памяти и ссылки на память в замаскированной форме.
Границы продуманы: ключ отправляется через POST (никогда не параметр URL, поэтому он не попадает в историю и журналы доступа), повторные неверные попытки блокируют клиента на пять минут, сессия повторно блокируется через ADMIN_SESSION_HOURS, и ни один маршрут здесь ничего не записывает и не раскрывает текст памяти или секреты ключей - владелец видит структуру учетной записи, а не ее содержимое. Если ADMIN_SECRET не задан, область вообще не существует.
Аутентификация (вход через Google)
Вход открывает доступ к ссылке на память: пользователь входит через Google, затем нажимает Copy memory link, чтобы подготовить свой проект по умолчанию + коллекцию + ключ API и получить URL для передачи агенту. Секрет показывается ровно один раз (сохраняется только его хэш): после этого целевая страница показывает ссылку замаскированной (через GET /api/me/link), а кнопка превращается в явное подтвержденное действие "получить новую ссылку" - перегенерация аннулирует старую ссылку, никогда не делая этого молча. Ключами можно управлять на странице /account: замаскированный список, даты создания/последнего использования, создание с меткой (однократное отображение) и отзыв. Чтобы настроить учетные данные Google:
Google Cloud Console → APIs & Services → OAuth consent screen - настройте его (External; добавьте свой email как тестового пользователя, пока приложение не проверено).
Credentials → Create credentials → OAuth client ID → Web application.
Добавьте Authorized redirect URI:
{PUBLIC_BASE_URL}/auth/callback- например,http://localhost:8000/auth/callbackдля локальной разработки иhttps://recall.select/auth/callbackдля продакшена (добавьте оба, если тестируете локально).Скопируйте Client ID и Client secret в
.env(GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET) и установите стабильныйSESSION_SECRET(python -c "import secrets; print(secrets.token_urlsafe(48))").
Локальный запуск
Полный стек (веб + Qdrant) через Docker Compose:
cp .env.example .env # then fill in MONGODB_URI
docker compose up --build
# open http://localhost:8000Или только приложение, используя свой собственный Qdrant/Mongo:
pip install -e ".[dev]"
uvicorn app.main:app --reloadТесты
pip install -e ".[dev]"
pytestCRUD-тесты запускаются с MongoDB в памяти (mongomock), а клиенты Qdrant/эмбеддинга имитируются - не требуются работающие бэкенды.
Развертывание
./deploy/deploy.shОдна и та же команда работает из двух мест - она определяет, где запущена:
С машины разработчика (или из агента): отправляет локальные коммиты, затем запускает развертывание на сервере через SSH-алиас
recall-server.На самом сервере (
setti@setti-server:~/recall_select$ ./deploy/deploy.sh): развертывает на месте, без SSH-перехода.
Оба пути запускают один и тот же скрипт - deploy/_server_deploy.sh: синхронизация master через git, пересборка стека Compose (FastAPI web + Qdrant), перезагрузка общего прокси Caddy (автоматический HTTPS для recall.select), удаление старых образов. MongoDB является удаленной/управляемой, поэтому переменная окружения auth/MONGODB_URI (см. .env) должна присутствовать на сервере.
Тот, кто запускает это на сервере, должен иметь доступ на чтение к репозиторию GitHub (авторизованный SSH-ключ в своем ~/.ssh) и быть членом группы docker - оба условия верны для claude-agent и setti. Скрипт автоматически регистрирует репозиторий как safe.directory в git, чтобы развертывающий, не являющийся владельцем репозитория, не был заблокирован ошибкой "dubious ownership".
Автоматические развертывания (CI)
Каждый push в master автоматически развертывается через GitHub Actions (.github/workflows/deploy.yml) - тот же процесс, что и выше, только запускаемый CI, а не человеком. Задача подключается по SSH к серверу и передает deploy/_server_deploy.sh через stdin, таким образом выполняя логику развертывания отправленного коммита. Развертывания сериализованы (concurrency), а кнопка Run workflow (workflow_dispatch) позволяет развернуть по требованию.
Одноразовая настройка - добавьте в Settings → Secrets and variables → Actions:
Секрет | Обязательно | Назначение |
| да | Приватный ключ, чья публичная половина находится в |
| да | Адрес сервера и пользователь SSH для развертывания. |
| нет | Порт SSH (по умолчанию |
| нет | Зафиксировать хост-ключ сервера; если не задано, CI доверяет ему при первом использовании через |
Секреты приложения (MONGODB_URI, OAuth и т.д.) остаются в .env на сервере - CI их никогда не видит.
Лицензия
Лицензировано в соответствии с GNU Affero General Public License v3.0. Если вы запускаете модифицированную версию в качестве сетевого сервиса, AGPL требует предоставить ее исходный код вашим пользователям. 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