Recall Select

recall.select
최소한의 에이전트 메모리 시스템 - 하나의 URL을 에이전트에 제공하면 거의 설정 없이 장기 기억을 얻을 수 있습니다. Qdrant + FastMCP + FastAPI/Bootstrap 기반.
전체 설계와 점진적 빌드 계획은 docs/specs/initial_specification.md에서, 주요 변경 사항 기록은 docs/specs/changelog.md에서 확인하세요.
작동 방식
메모리는 벡터로 저장됩니다. 각 메모리 저장소는 Qdrant 컬렉션이며, (사용자, 프로젝트) 쌍과 일대일로 매핑됩니다. 해당 벡터 주변의 메타데이터(사용자, API 키, 프로젝트, 컬렉션별 사용량/제한 통계)는 MongoDB에 저장됩니다.
flowchart LR
agent[Agent] --> web[FastAPI / MCP]
web <-->qdrant[Qdrant]
web <--> mongo[MongoDB]
web <--> embed[Embedding API]Qdrant 컬렉션은 지연 생성됩니다. 즉, (사용자, 프로젝트) 쌍에 첫 번째 메모리가 저장될 때까지 Qdrant에 접근하지 않습니다.
Related MCP server: LedgerMem MCP Server
아키텍처
app/main.py- FastAPI 앱. Bootstrap 랜딩 페이지를 제공하고, 시작 시 Mongo 인덱스가 존재하는지 확인합니다(콜드/원격 DB에 대해 내성적).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- 연결된 에이전트는 클라이언트 측에서 관계 추론을 수행하며(명시적 요청 시에만), 이러한 도구는 결과를 수집하거나 탐색합니다. 동일한 키는 키가 없는/mcp엔드포인트에 대해Authorization: Bearer로 전송되어 URL/로그에서 비밀을 숨길 수 있습니다.{...}/m/{key}.md(app/api/connect.py에 있음)는 일치하는 설정 지침(두 형식 모두)을 제공합니다.app/dependencies.py- 핵심 DI 컨테이너(injector). 공유 싱글톤(Qdrant 클라이언트, Mongo 클라이언트/DB, 원격 임베더)을 구성합니다. FastAPI 종속성(app/api/deps.py) 및 시작은app_container에서 해결하며, 자체적으로 클라이언트를 빌드하지 않습니다.app/services/- 서비스 계층(HTTP/라우트 코드 없음, I/O만):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))는 현재 제한된 스크롤 및 스캔입니다. 역방향 탐색이 빈번해지면 해결 방법은 Qdrant 페이로드 인덱스를_semantics.relations[].target에 생성하고(create_payload_index, 키워드 스키마) 스캔 대신 필터링된 쿼리를 사용하는 것입니다. 동일한 저장소, 인덱스만 추가되며 스키마는 변경되지 않습니다.mongo.py- Mongo 클라이언트,get_db(),ensure_indexes()(고유 복합 인덱스로 일대일(사용자, 프로젝트)규칙을 적용).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(제시된 토큰을 해시하고 다이제스트와 일치시킴, MCP 인증 게이트에서record_use=True는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-(사용자, 프로젝트) ↔ 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- 요금제 카탈로그 및 Monobank의invoiceId로 키가 지정된 결제 기록. 체크아웃 시record_pending;apply_webhook은success시 구매자의tier를 한 번 전환합니다(재시도/중복에 대해 멱등적).reconcile은 웹훅이 놓친 것을 정리합니다(아래 참조). 티어는 시간 제한이 있습니다:grant_tier는 자격이 부여되는 유일한 장소(유료 송장 또는 소유자 호의)이며,tier_expires_at및tier_grants의 감사 행을 기록합니다.effective_tier(user)는 모든 검사가 읽어야 하는 값입니다. 저장된paid_2x의 날짜가 지나면 무료 계정이기 때문입니다. 또한 티어별 허용량의 단일 진실 공급원:call_allowance(tier)/project_allowance(tier)(None= 무제한, 알 수 없는 티어는 무료로 대체).usage.py- 월별 호출 측정기 및 가격 모델 게이트. 승인된 모든 저장/검색/삭제는(사용자, 달력 월)당usage행에 집계됩니다.check_call_allowed는 티어의 월별call_allowance가 소진되면 호출을 거부하여QuotaExceeded를 발생시킵니다.memory.py에서 적용되므로(MCP 도구와 HTTP 메모리 API 모두 포함)app/main.py에 의해 HTTP 429로 매핑됩니다. 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, app/translations/*.yml을 통한 i18n): / 랜딩, /plans, /account(로그인됨), /docs/integrations 가이드. FastAPI의 내장 API 문서는 /docs에서 /api/docs로 이동됩니다(/api/redoc, /api/openapi.json). 따라서 공개 사이트가 /docs를 소유합니다.
결제는 **app/api/payments.py**의 HTTP 계층을 통해 처리됩니다: POST /api/me/checkout(로그인됨)은 송장을 생성하고 Monobank pay_url을 반환합니다. 확인된 POST /webhooks/monobank는 티어를 부여합니다. GET /payment/success|fail은 화장용 브라우저 반환 페이지입니다(자격은 웹훅 기반이며, 이 페이지는 아님).
자격은 웹훅에만 의존하지 않습니다. Monobank는 각 상태 변경을 한 번만 보내고 다시 보내지 않으므로, 재시작이나 프록시 결함으로 인해 콜백이 손실되면 지불한 고객이 이전 티어에 남게 되며, 우리 측에서 이를 인지할 방법이 없습니다. 따라서 앱은 또한 가져옵니다: PAYMENT_RECONCILE_MINUTES마다 백그라운드 스윕(billing.reconcile_with_monobank, app/main.py 수명 주기에서 시작)이 5분 후에도 진행 중인 각 결제의 실제 상태를 가져와 동일한 apply_webhook 전환을 통해 푸시합니다. 푸시와 풀은 서로에 대해 멱등적입니다. 먼저 도착하는 쪽이 티어를 부여하고, 다른 쪽은 아무 작업도 하지 않습니다. 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을 기록하므로 페이지는 여전히 무엇이 종료되었는지 표시할 수 있음). 갱신은 요금제 페이지에서 사용하는 동일한 /api/me/checkout에 게시하며, 보유한 요금제로 미리 설정됩니다. 아직 이메일은 없습니다. 프롬프트는 사이트를 방문하는 사용자에게만 도달합니다.
모든 CRUD 함수는 선택적 db=/client= 인수를 사용하므로 라이브 백엔드 없이 테스트에서 구동할 수 있습니다.
구성
환경 변수로 설정합니다(로컬 .env는 자동 로드되며, 절대 커밋하지 마세요. .env.example 참조):
변수 | 기본값 | 목적 |
| (필수) | 원격 관리형 MongoDB 연결 문자열. |
|
| 데이터베이스 이름. |
|
| Qdrant 엔드포인트 (내부 Compose 네트워크). |
| (로컬 없음, 프로덕션 필수) | 앱과 Qdrant 간 공유 비밀. Compose는 |
|
| 모든 컬렉션의 벡터 차원. 원격 임베더는 (API |
| (필수) | 원격 임베딩 API의 API 키. |
|
| OpenAI 호환 임베딩 API 기본 URL. |
| (로그인 필수) | Google OAuth 2.0 웹 클라이언트 ID. |
| (로그인 필수) | Google OAuth 2.0 클라이언트 시크릿. |
| (개발 폴백) | 세션 쿠키에 서명합니다. 프로덕션에서는 안정적인 값을 설정하세요. |
|
| 공개 출처; 메모리 링크 + OAuth 리디렉션 URI를 구성합니다. |
|
| uvicorn이 신뢰하는 |
| (결제 필수) | Monobank 인수 머천트 토큰. mcp-api.net 플랫폼과 공유 - 동일한 머천트, 하나의 계정; 청구서는 |
|
| 구매자가 결제 후 브라우저가 이동하는 URL. |
|
| 등급을 부여하는 서버 간 콜백. 공개적으로 접근 가능해야 합니다. |
|
| 머천트 공개 키에 대한 웹훅의 |
|
| Monobank에서 진행 중인 결제의 실제 상태를 가져오는 빈도입니다. 웹훅이 손실되어도 결제 고객이 피해를 보지 않도록 합니다. |
| (설정 안 함 - 영역 비활성화) |
|
|
| 잠금 해제된 관리자 세션이 다시 잠기기까지 지속되는 시간입니다. |
소유자 관리자 영역 (/admin)
지원 및 사용자 화면 확인을 위해 모든 사용자의 개인 영역을 읽기 전용으로 볼 수 있는 창입니다. ADMIN_SECRET을 설정하고 (python -c "import secrets; print(secrets.token_urlsafe(32))"로 생성), 웹 컨테이너를 다시 만든 다음 {PUBLIC_BASE_URL}/admin을 열고 세션당 한 번 키를 입력하세요. /admin/users는 모든 계정을 나열하며 (이메일, 이름 또는 사용자 ID로 검색 가능), 각 행은 해당 사용자의 요금제, 이 기간 사용량, 메모리 카운트가 있는 프로젝트, 마스킹된 형태의 메모리 링크를 엽니다.
경계는 의도적입니다. 키는 POST로 제출되며 (URL 파라미터가 아니므로 기록 및 액세스 로그에 남지 않음), 반복된 잘못된 추측은 클라이언트를 5분 동안 잠그고, 세션은 ADMIN_SESSION_HOURS 후에 자동으로 다시 잠깁니다. 이 경로는 아무것도 쓰거나 메모리 텍스트 또는 키 비밀을 공개하지 않습니다 - 소유자는 계정의 형태만 볼 수 있고 내용은 볼 수 없습니다. ADMIN_SECRET이 설정되지 않으면 이 영역은 아예 존재하지 않습니다.
인증 (Google 로그인)
로그인은 메모리 링크를 게이트합니다. 사용자가 Google로 로그인한 다음 메모리 링크 복사를 클릭하여 기본 프로젝트 + 컬렉션 + API 키를 프로비저닝하고 에이전트에 제공할 URL을 얻습니다. 비밀은 정확히 한 번만 표시됩니다 (해시만 저장됨): 이후 랜딩 페이지는 링크를 마스킹하여 표시하고 (GET /api/me/link를 통해), 버튼은 명시적이고 확인된 "새 링크 받기"로 바뀝니다. 재생성은 이전 링크를 자동으로 무효화합니다. 키는 /account에서 관리됩니다: 마스킹된 목록, 생성/마지막 사용 날짜, 레이블로 생성 (한 번 공개), 취소. Google 자격 증명을 설정하려면:
Google Cloud Console → APIs & Services → OAuth 동의 화면 - 구성합니다 (외부; 미검증 상태에서 테스트 사용자로 이메일 추가).
사용자 인증 정보 → 사용자 인증 정보 만들기 → OAuth 클라이언트 ID → 웹 애플리케이션.
승인된 리디렉션 URI 추가:
{PUBLIC_BASE_URL}/auth/callback- 예: 로컬 개발의 경우http://localhost:8000/auth/callback, 프로덕션의 경우https://recall.select/auth/callback(로컬에서 테스트하는 경우 둘 다 추가).클라이언트 ID와 클라이언트 시크릿을
.env에 복사합니다 (GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET). 안정적인SESSION_SECRET을 설정합니다 (python -c "import secrets; print(secrets.token_urlsafe(48))").
로컬 실행
Docker Compose를 통한 전체 스택 (웹 + Qdrant):
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 테스트는 인메모리 Mongo (mongomock)에 대해 실행되며 Qdrant/임베딩 클라이언트는 가짜입니다 - 라이브 백엔드가 필요하지 않습니다.
배포
./deploy/deploy.sh동일한 명령이 두 곳에서 작동합니다. 실행 위치를 감지합니다:
개발 머신 (또는 에이전트 상자)에서: 로컬 커밋을 푸시한 다음
recall-serverSSH 별칭을 통해 서버에서 배포를 실행합니다.서버 자체 (
setti@setti-server:~/recall_select$ ./deploy/deploy.sh)에서: 제자리에서 배포하며 SSH 홉이 없습니다.
두 경로 모두 동일한 작업자 deploy/_server_deploy.sh를 실행합니다: master의 git 동기화, Compose 스택 재구축 (FastAPI web + Qdrant), 공유 Caddy 프록시 재로드 (recall.select에 대한 자동 HTTPS), 오래된 이미지 정리. MongoDB는 원격/관리형이므로 서버에 인증/MONGODB_URI 환경 변수 (.env 참조)가 있어야 합니다.
서버에서 실행하는 사람은 저장소에 대한 GitHub 풀 액세스 (자신의 ~/.ssh에 승인된 SSH 키)와 docker 그룹의 구성원 자격이 필요합니다. claude-agent와 setti 모두 해당됩니다. 작업자는 저장소를 git safe.directory로 자동 등록하므로 저장소 소유자가 아닌 배포자가 "의심스러운 소유권"으로 인해 차단되지 않습니다.
자동 배포 (CI)
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