Recall Select

recall.select
Ein minimales agentisches Gedächtnissystem – füttere eine URL an einen beliebigen Agenten und es erhält ein Langzeitgedächtnis mit nahezu keinem Einrichtungsaufwand. Basierend auf Qdrant + FastMCP + FastAPI/Bootstrap.
Siehe docs/specs/initial_specification.md für das vollständige Design und den inkrementellen Bauplan, sowie docs/specs/changelog.md für eine laufende Aufzeichnung bemerkenswerter Änderungen.
Funktionsweise
Erinnerungen werden als Vektoren gespeichert. Jeder Erinnerungsspeicher ist eine Qdrant-Collection, die eins-zu-eins auf ein (user, project)-Paar abgebildet wird. Metadaten um diese Vektoren – Benutzer, API-Schlüssel, Projekte und Nutzungs-/Limit-Statistiken pro Collection – leben in MongoDB.
flowchart LR
agent[Agent] --> web[FastAPI / MCP]
web <-->qdrant[Qdrant]
web <--> mongo[MongoDB]
web <--> embed[Embedding API]Qdrant-Collections werden lazy erstellt: Nichts berührt Qdrant, bis die erste Erinnerung in ein (user, project)-Paar gespeichert wird.
Related MCP server: LedgerMem MCP Server
Architektur
app/main.py– FastAPI-App. Liefert die Bootstrap-Landingpage und stellt beim Start sicher, dass die Mongo-Indizes existieren (tolerant gegenüber einer kalten/entfernten DB).app/mcp_server.py– der MCP-Server hinter dem Erinnerungslink. Ein MCP-Client eines Agenten zeigt auf{PUBLIC_BASE_URL}/m/{key}(Streamable HTTP, zustandslos, JSON-Antworten); der API-Schlüssel im Pfad ist die gesamte Berechtigung und begrenzt die Werkzeuge auf das Standardprojekt des Schlüsselinhabers. Grundlegende Werkzeuge:store_memory/recall_memory/delete_memory. Semantische Werkzeuge (siehevector_semantics.py):link_memories/unlink_memories/annotate_memory/memory_connections/recall_connected– der verbundene Agent führt die Beziehungslogik clientseitig durch (nur auf explizite Anforderung) und diese nehmen das Ergebnis auf oder durchlaufen es. Derselbe Schlüssel kann stattdessen alsAuthorization: Bearergegen den schlüssellosen/mcp-Endpunkt gesendet werden, um das Geheimnis aus der URL/den Logs fernzuhalten.{...}/m/{key}.md(inapp/api/connect.py) liefert die passenden Einrichtungsanweisungen (beide Formen).app/dependencies.py– der zentrale DI-Container (injector). Erstellt die gemeinsamen Singletons (Qdrant-Client, Mongo-Client/DB, den entfernten Embedder). FastAPI-Abhängigkeiten (app/api/deps.py) und der Startup beziehen ausapp_container, anstatt Clients selbst zu bauen.app/services/– die Service-Schicht (kein HTTP/Route-Code, nur I/O):qdrant_store.py– Qdrant-Client +ensure_collection/upsert_memory/search/delete_memory, plus die Punkt-Primitive, die die semantische Schicht benötigt (neighbors,scroll_points,retrieve_points,set_payload).vector_semantics.py– die Vektorspeicher-Dienstprogrammschicht: behandelt einen Speicher als Bedeutungsgraph. Ein reservierter_semantics-Namespace in der Nutzlast jedes Punktes enthält Deixis-Anker (Besitzer, gespeichert am; zum Speicherzeitpunkt geschrieben), clientseitig extrahierte Entitäten und clientseitig deklarierte typisierte Beziehungen (upsert_relationsvalidiert und speichert sie – keine LLM-Aufrufe serverseitig). Deklarierte Beziehungen tragen zwei Qualitätsabstufungen:confidence(0-1], skaliert die Traversierungsstärke der Kante) undvalid_till(ISO 8601; abgelaufene Kanten werden von jedem Lesepfad ignoriert, sodass veraltete Strukturen sich selbst zurückziehen). Hygiene:remove_relationslöscht falsche Kanten (das korrigierende Gegenstück zuupsert_relations), undmemory.delete_memoryruftprune_relations_toauf, sodass nach dem Löschen einer Erinnerung keine hängenden Kanten überleben. Steckbare Linsen (topical/temporal/entity/declared) leiten typisierte Kanten ab; darauf aufbauend sitzensemantic_graph(Multigraph),spreading_activation(Abruf durch Verbindung),concept_clusters(emergente Ontologie) undinfer_relation(zuerst deklarierte Wahrheit, danach geometrische Heuristiken). Performance-Notiz: Die Suche nach eingehenden Kanten (relations_of(include_incoming=True)) ist heute ein begrenzter Scroll-und-Scan. Falls die Rückwärtstraversierung heiß wird, ist die Lösung ein Payload-Index in Qdrant auf_semantics.relations[].target(create_payload_index, Keyword-Schema) und eine gefilterte Abfrage anstelle des Scans – derselbe Speicher, nur ein Index; am Schema ändert sich nichts.mongo.py– Mongo-Client,get_db()undensure_indexes()(erzwingt die Eins-zu-eins-Regel(user, project)mit einem eindeutigen zusammengesetzten Index).users.py–add_user,get_user,get_user_by_email,update_user.api_keys.py– benutzergebundene Schlüssel, als SHA-256-Hash gespeichert (der Klartext wird einmalig vonadd_api_keyzurückgegeben und nie persistiert):add_api_key,delete_api_key,delete_user_keys,list_api_keys,get_labeled_key,get_by_key(hasht das präsentierte Token und gleicht mit dem Digest ab;record_use=Trueam MCP-Auth-Gate stampeltlast_used_at). Im Ruhezustand behält jeder Schlüssel auch nicht-geheime Anzeigehinweise –key_prefix+key_last4, dargestellt vonmasked()alsrs_ab12…wxyz– sodass Schlüssel aufgelistet und unterschieden werden können, ohne das Geheimnis jemals wieder offenzulegen.projects.py–add_project,get_project,list_projects,update_project,delete_project.collections.py– das(user, project) ↔ Qdrant-Collection-Register.collection_name(user_id, project_id)ist der interne Namensstandard (rs_{user}_{project}); verfolgtpoints_count/calls_countfür Limits & Statistiken.collection_provisioning.py– der zweiseitige Schrittcreate_collection/destroy_collection. Eine Collection existiert nur, wenn sowohl ihre Mongo-Registry-Zeile als auch ihre zugrunde liegende Qdrant-Collection existieren; dies setzt dascollections-Register mitqdrant_storezu einer atomaren, idempotenten Operation zusammen, sodass die beiden Speicher nie aus dem Tritt geraten. Die Erstellung ist lazy, daher ist der einzige aufrufende Aufrufer der erste Speicherschreibvorgang (memory.store_memory); das Löschen der Collection-API verwendetdestroy_collection.embeddings.py– dieEmbedder-Abstraktion;embeddings_remote.py– das konkrete Text→Vektor-Backend (entfernte Embedding-API, z. B. DeepInfra).monobank.py– minimaler Monobank-Acquiring-Client (create_invoice,fetch_invoice_status) plus Webhook-Authentifizierung (fetch_pubkey/verify_signature, ECDSA-SHA256 über den rohen Body). Verwendet das mcp-api.net-Merchant-Token; recall.select besitzt seine eigenen Invoice/Redirect/Webhook.billing.py– der Plankatalog und der Zahlungsdatensatz, der an MonobanksinvoiceIdgebunden ist.record_pendingbeim Checkout;apply_webhookschaltet dentierdes Käufers einmalig beisuccessum (idempotent gegenüber Wiederholungen/Duplikaten);reconcilebegleicht, was der Webhook verpasst hat (unten). Ein Tier ist zeitlich begrenzt:grant_tierist der einzige Ort, an dem Berechtigungen jemals vergeben werden (bezahlte Rechnung oder Goodwill des Besitzers), und schreibttier_expires_atplus eine Audit-Zeile intier_grants;effective_tier(user)ist das, was jede Prüfung lesen muss, da ein gespeichertespaid_2x, dessen Datum abgelaufen ist, ein kostenloses Konto bedeutet. Auch die einzige Quelle der Wahrheit für Pro-Tier-Zulagen:call_allowance(tier)/project_allowance(tier)(None= unbegrenzt; unbekannte Tiers fallen auf free zurück).usage.py– der monatliche Aufrufzähler und das Preismodell-Gate. Jeder akzeptierte Store/Recall/Delete wird in eineusage-Zeile pro(user, Kalendermonat)eingetragen;check_call_allowedlehnt einen Aufruf ab, sobald das monatlichecall_allowancedes Tiers aufgebraucht ist, und wirftQuotaExceeded. Durchgesetzt inmemory.py(sodass sowohl die MCP-Werkzeuge als auch die HTTP-Speicher-API abgedeckt sind) und inapp/main.pyauf HTTP 429 abgebildet; der MCP-Transport zeigt es als Werkzeugfehler an. Getrennt von dem lebenslangencollections.calls_count.account.py– die schreibgeschützte Momentaufnahme, die die angemeldete/account-Seite anzeigt (Plan, monatliche Nutzung, gespeicherte Anzahlen pro Projekt und die API-Schlüsselliste in maskierter Form mit Erstellungs-/Letztverwendungsdaten), zusammengesetzt ausbilling/usage/projects/collections/api_keys.docs.py– Inhalt für die öffentlichen/docs-Integrationsleitfäden. Erstellt die MCP-Client-Konfiguration an einem Ort (mcp_config/mcp_config_json), wiederverwendet sowohl von den Dokumentationsseiten als auch vonapp/api/connect.pys pro-Schlüssel.md, sodass die beiden nie auseinanderdriften.INTEGRATIONSist das Leitfadenregister (füge eine Seite hinzu, indem du einen Eintrag hinzufügst).
Öffentliche Seiten (bereitgestellt von app/main.py, Bootstrap + Jinja, i18n über app/translations/*.yml): / Landing, /plans, /account (angemeldet) und die /docs/integrations-Leitfäden. FastAPIs eingebaute API-Dokumentation wird von /docs nach /api/docs verschoben (/api/redoc, /api/openapi.json), sodass die öffentliche Seite /docs besitzt.
Zahlungen laufen über die HTTP-Schicht in app/api/payments.py: POST /api/me/checkout (angemeldet) erstellt die Rechnung und gibt die Monobank pay_url zurück; der verifizierte POST /webhooks/monobank gewährt den Tier; GET /payment/success|fail sind die kosmetischen Browser-Rückgabeseiten (Berechtigung erfolgt über Webhook, niemals über diese).
Die Berechtigung hängt nicht allein vom Webhook ab. Monobank sendet jede Statusänderung einmal und sendet sie nie erneut, sodass ein durch einen Neustart oder einen Proxy-Aussetzer verlorener Callback einen Kunden, der bezahlt hat, auf seinem alten Tier belassen würde, ohne dass auf unserer Seite etwas bemerkt wird. Daher zieht die App auch ab: Alle PAYMENT_RECONCILE_MINUTES Minuten holt ein Hintergrund-Sweep (billing.reconcile_with_monobank, gestartet in der app/main.py-Lebensdauer) den tatsächlichen Status jeder noch ausstehenden Zahlung nach fünf Minuten ab und schiebt ihn durch den gleichen apply_webhook-Übergang. Push und Pull sind gegeneinander idempotent – welcher auch immer zuerst landet, gewährt den Tier, der andere ist ein No-Op. Eine Zeile in payments wird geschrieben, wenn der Checkout beginnt, also bedeutet created "Zahlungsseite geöffnet", nicht "bezahlt"; /admin/payments zeigt diese Unterscheidung explizit.
Ein Kauf erwirbt einen Monat (SUBSCRIPTION_DAYS), nicht für immer: grant_tier stampelt tier_expires_at, derselbe Hintergrund-Loop setzt abgelaufene Konten auf free zurück (downgrade_expired), und die Kontoseite zeigt das Datum, an dem der Plan ausläuft. Nichts verlängert sich automatisch – der Benutzer kauft erneut, und ein Kauf während noch Guthaben besteht verlängert das Fenster, anstatt es neu zu starten. Die Berechtigung wird über effective_tier gelesen, sodass eine abgelaufene Gewährung sofort aufhört auszuzahlen, noch bevor der Sweep das gespeicherte Feld überschreibt.
Da sich nichts von selbst verlängert, fragt die App: billing.renewal_state(user) treibt eine Aufforderung auf /account an – eine Warnung mit einem Ein-Klick-Verlängerungs-Button in den letzten RENEWAL_WARNING_DAYS (7) eines Plans und eine "Ihr Plan ist abgelaufen, verlängern Sie ihn"-Aufforderung für LAPSED_PROMPT_DAYS (30) danach (der Downgrade-Sweep zeichnet lapsed_tier / tier_lapsed_at auf, sodass die Seite immer noch sagen kann, was abgelaufen ist). Verlängerung postet an denselben /api/me/checkout, den die Pläne-Seite verwendet, voreingestellt auf den Plan, den sie hatten. Es gibt noch keine E-Mail – die Aufforderung erreicht nur Benutzer, die die Seite besuchen.
Jede CRUD-Funktion nimmt ein optionales db=/client=-Argument, sodass sie in Tests ohne ein Live-Backend gesteuert werden kann.
Konfiguration
Wird über die Umgebung gesetzt (eine lokale .env wird automatisch geladen; niemals committen – siehe .env.example):
Variable | Standard | Zweck |
| (erforderlich) | Verbindungszeichenfolge für ein entferntes, verwaltetes MongoDB. |
|
| Name der Datenbank. |
|
| Qdrant-Endpunkt (internes Compose-Netzwerk). |
| (lokal keine; in Produktion erforderlich) | Gemeinsames Geheimnis zwischen App und Qdrant. Compose setzt daraus Qdrants |
|
| Vektordimension für jede Collection. Der entfernte Embedder wird (über den Parameter |
| (erforderlich) | API-Schlüssel für die entfernte Embedding-API. |
|
| Basis-URL der OpenAI-kompatiblen Embeddings-API. |
| (für die Anmeldung erforderlich) | Google-OAuth-2.0-Web-Client-ID. |
| (für die Anmeldung erforderlich) | Google-OAuth-2.0-Client-Secret. |
| (Entwicklungs-Fallback) | Signiert das Session-Cookie. Setzen Sie in Produktion einen stabilen Wert. |
|
| Öffentliche Origin; erstellt den Memory-Link und die OAuth-Redirect-URI. |
|
| Peers, deren |
| (für Zahlungen erforderlich) | Monobank-Händler-Token für Acquiring. Wird mit der Plattform mcp-api.net geteilt - derselbe Händler, ein Konto; Rechnungen werden über |
|
| Wohin der Browser des Käufers nach der Bezahlung zurückkehrt. |
|
| Server-zu-Server-Callback, der die Stufe gewährt. Muss öffentlich erreichbar sein. |
|
| Überprüft die |
|
| Wie oft der tatsächliche Status laufender Zahlungen von Monobank abgerufen wird, damit ein verlorener Webhook einen zahlenden Kunden nicht im Stich lässt. |
| (nicht gesetzt - Bereich deaktiviert) | Entsperrt den Admin-Bereich des Eigentümers unter |
|
| Wie lange eine entsperrte Admin-Sitzung dauert, bevor sie sich wieder sperrt. |
Admin-Bereich des Eigentümers (/admin)
Ein schreibgeschütztes Fenster in den persönlichen Bereich eines beliebigen Benutzers, für den Support und um zu sehen, was ein Benutzer sieht. Setzen Sie ADMIN_SECRET (generieren: python -c "import secrets; print(secrets.token_urlsafe(32))"), erstellen Sie den Web-Container neu und öffnen Sie dann {PUBLIC_BASE_URL}/admin und geben Sie den Schlüssel einmal pro Sitzung ein. /admin/users listet jedes Konto auf – durchsuchbar nach E-Mail, Name oder Benutzer-ID – und jede Zeile öffnet den Plan dieses Benutzers, die Nutzung in diesem Zeitraum, Projekte mit ihren Memory-Zählwerten und Memory-Links in maskierter Form.
Die Grenzen sind bewusst gesetzt: Der Schlüssel wird per POST übermittelt (niemals als URL-Parameter, damit er nicht in Verlauf und Zugriffsprotokollen auftaucht), wiederholte falsche Eingaben sperren einen Client für fünf Minuten aus, die Sitzung sperrt sich nach ADMIN_SESSION_HOURS von selbst wieder, und keine Route hier schreibt etwas oder gibt Memory-Text oder Schlüsselgeheimnisse preis – der Eigentümer sieht die Form eines Kontos, nicht dessen Inhalt. Wenn ADMIN_SECRET nicht gesetzt ist, existiert der Bereich überhaupt nicht.
Authentifizierung (Google-Anmeldung)
Die Anmeldung schützt den Memory-Link: Ein Benutzer meldet sich mit Google an, klickt dann auf Memory-Link kopieren, um sein Standardprojekt + Collection + API-Schlüssel einzurichten und die URL zu erhalten, die einem Agenten übergeben wird. Das Geheimnis wird genau einmal angezeigt (nur sein Hash wird gespeichert): Danach zeigt die Landingpage den Link maskiert (über GET /api/me/link) und der Button wird zu einem expliziten „Neuen Link erhalten“, der eine Bestätigung verlangt – die Neuerstellung macht den alten Link ungültig, niemals stillschweigend. Schlüssel werden unter /account verwaltet: maskierte Liste, Erstellungs- und letzte-Verwendungsdaten, Erstellen mit Label (einmalig anzeigen) und Widerrufen. So richten Sie die Google-Anmeldedaten ein:
Google Cloud Console → APIs & Services → OAuth consent screen – konfigurieren Sie ihn (External; fügen Sie Ihre E-Mail-Adresse als Testbenutzer hinzu, solange die App noch nicht verifiziert ist).
Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID → Webanwendung.
Fügen Sie eine Autorisierte Weiterleitungs-URI hinzu:
{PUBLIC_BASE_URL}/auth/callback– z. B.http://localhost:8000/auth/callbackfür die lokale Entwicklung undhttps://recall.select/auth/callbackin Produktion (fügen Sie beide hinzu, wenn Sie lokal testen).Kopieren Sie die Client-ID und das Client-Secret in
.env(GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET) und setzen Sie ein stabilesSESSION_SECRET(python -c "import secrets; print(secrets.token_urlsafe(48))").
Lokal ausführen
Der vollständige Stack (Web + Qdrant) über Docker Compose:
cp .env.example .env # then fill in MONGODB_URI
docker compose up --build
# open http://localhost:8000Oder nur die App, mit Ihrem eigenen Qdrant/Mongo:
pip install -e ".[dev]"
uvicorn app.main:app --reloadTests
pip install -e ".[dev]"
pytestCRUD-Tests laufen gegen ein In-Memory-Mongo (mongomock); Qdrant-/Embedding-Clients werden simuliert – es sind keine Live-Backends erforderlich.
Deployment
./deploy/deploy.shDerselbe Befehl funktioniert von zwei Orten aus – er erkennt, wo er ausgeführt wird:
Von einem Entwicklungsrechner (oder der Agent-Box): schiebt lokale Commits und führt dann das Deployment auf dem Server über den SSH-Alias
recall-serveraus.Auf dem Server selbst (
setti@setti-server:~/recall_select$ ./deploy/deploy.sh): führt das Deployment direkt vor Ort aus, ohne SSH-Sprung.
Beide Pfade führen denselben Worker aus – deploy/_server_deploy.sh: git-Sync von master, Neuaufbau des Compose-Stacks (FastAPI web + Qdrant), Neuladen des gemeinsamen Caddy-Proxys (automatisches HTTPS für recall.select), Bereinigen alter Images. MongoDB ist entfernt/verwaltet, daher muss die Auth-/MONGODB_URI-Umgebungsvariable (siehe .env) auf dem Server vorhanden sein.
Wer es auf dem Server ausführt, benötigt GitHub-Lesezugriff auf das Repository (einen autorisierten SSH-Schlüssel in ~/.ssh) und die Mitgliedschaft in der Gruppe docker – beides trifft auf claude-agent und setti zu. Der Worker registriert das Repository automatisch als git-safe.directory, sodass ein Deployer, der nicht der Eigentümer des Repositorys ist, nicht durch „dubious ownership“ blockiert wird.
Automatische Deployments (CI)
Jeder Push auf master löst über GitHub Actions automatisch ein Deployment aus (.github/workflows/deploy.yml) – derselbe Ablauf wie oben, nur von CI statt von einer Person ausgelöst. Der Job verbindet sich per SSH mit dem Server und leitet deploy/_server_deploy.sh über stdin weiter, sodass die Deploy-Logik des gepushten Commits ausgeführt wird. Deployments werden serialisiert (concurrency), und ein Run workflow-Button (workflow_dispatch) ermöglicht ein Deployment auf Abruf.
Einmalige Einrichtung – unter Settings → Secrets and variables → Actions hinzufügen:
Secret | Erforderlich | Zweck |
| ja | Privater Schlüssel, dessen öffentlicher Teil sich in |
| ja | Serveradresse und SSH-Benutzer für das Deployment. |
| nein | SSH-Port (Standard |
| nein | Hostkey des Servers pinnen; wenn nicht gesetzt, vertraut CI ihm bei der ersten Verwendung über |
App-Secrets (MONGODB_URI, OAuth usw.) bleiben in der .env des Servers – CI sieht sie nie.
Lizenz
Lizenziert unter der GNU Affero General Public License v3.0. Wenn Sie eine modifizierte Version als Netzwerkdienst betreiben, verlangt die AGPL, dass Sie deren Quellcode Ihren Nutzern zur Verfügung stellen. 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