localmind
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@localmindask: what did I learn yesterday?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
localmind
로컬 Claude Code / Codex CLI 구독을 토대로, 메터드 API 0원의 완결형 로컬 AI 스택을 repo 하나로 제공합니다. 모든 것이 로컬·독립 실행(중앙 서버·공유 계정 의존 0)이며, OpenAI·Anthropic 호환 API + 임베딩 + mem0 메모리 + second-brain RAG + MCP를 한 번에 제공합니다.
LLM API — OpenAI(
/v1/chat/completions)·Anthropic(/v1/messages) 호환. 기존 코드의base_url만 바꾸면 됨.임베딩 — 로컬 bge-m3 (OpenAI
/v1/embeddings호환)메모리 — mem0 (진화하는 사실 기억)
second-brain — 내 마크다운 노트에 대한 RAG
MCP 도구 — Cursor/Claude Desktop/Cline에서 위 기능을 도구로 사용
🧩 처음 보는 용어가 많다면 — 게이트웨이·임베딩·RAG·MCP를 딱 하나의 비유(내 컴퓨터 안의 1인 비서실)로 5분 만에 풀어주는 입문서부터: 👉 비유로 이해하기
독립 실행 원칙: 각 인스턴스는 완전히 자립적입니다. 그 머신의 로컬 스택 (gateway·임베딩·메모리·brain) + 그 머신 자신의
claude/codex로그인 + localhost로만 동작하며, 중앙 homeserver나 공유 계정에 의존하지 않습니다(단일 장애점·ToS 회피). 여러 명/여러 서버는 각자 독립 인스턴스로 돌리고, 필요하면 원격 MCP(URL+토큰)로 접속만 합니다 — 추론은 항상 각 인스턴스의 자기 계정으로. 진화 방향은 ROADMAP.md.
HTTP API ┬─ /v1/chat/completions · /v1/messages → claude/codex CLI
├─ /v1/embeddings → bge-m3
└─ OpenMemory REST → mem0 + pgvector
MCP ───── ask · remember/recall · capture_note/search_notes/ask_brainQuickstart
0) 전제
Node.js ≥ 20, Docker
호스트에 로그인된
claude/codexCLI
1) 설치 & 기동
git clone https://github.com/shaul1991/localmind && cd localmind
make install build # 의존성 + dist 빌드(로컬 MCP용)
make up # Docker 스택 기동(게이트웨이+메모리)
# chat :8787 · 게이트웨이 :4000 · 메모리 :8767 (최초 빌드/모델 pull은 수 분)운영은 전부
make로 일관됩니다:make up(기동) ·make health(점검) ·make logs·make down. 전체 목록은make help. (아래 문서의docker compose --profile ...는 make가 실행하는 내부 명령으로, 세분 제어가 필요할 때 참고)
2) API로 쓰기 (base_url만 교체)
curl http://localhost:8787/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model":"sonnet","messages":[{"role":"user","content":"안녕"}]}'OpenAI/Anthropic SDK는 base_url만 위 주소로 바꾸면 그대로 동작 → 사용 예시.
3) MCP로 쓰기 (개인 두뇌)
Cursor .cursor/mcp.json / Claude Desktop claude_desktop_config.json / Cline MCP 설정에:
{ "mcpServers": { "localmind": {
"command": "node",
"args": ["/절대경로/localmind/dist/mcp.js"],
"env": { "NOTES_DIR": "/내/노트/폴더", "OPENMEMORY_USER": "내이름" }
}}}→ 호스트가 ask·remember/recall·capture_note/search_notes/ask_brain 도구를 갖습니다.
NOTES_DIR를 기존 .md 노트 폴더(예: second-brain-mesh)로 가리키면 그 지식으로 바로 RAG.
4) 검증
make smoke # API + MCP + brain 스모크 한 번에
make health # 엔드포인트 상태(:8787 / :4000 / :8767)더 가볍게: 채팅 API만 쓰려면
docker compose up -d --build(게이트웨이/메모리 프로파일 생략 — 이 경우만 raw).
Related MCP server: ensemble-mcp
온보딩 (역할별)
모든 인스턴스는 각자 독립으로 동작합니다(중앙 서버·공유 계정 의존 0). 역할에 맞는 경로만 따라가세요.
① 개인 — 내 PC에서 바로 쓰기
git clone https://github.com/shaul1991/localmind && cd localmindmake install build && make up(전제: 호스트에claude/codex로그인)make health로 확인 → Cursor/Claude Desktop에 MCP 설정(아래 MCP 서버 섹션)NOTES_DIR를 내 노트 폴더로 가리키면 그 지식으로 바로 RAG
② 인프라 운영 — 서버별로 관리
서버마다
MCP_INSTANCE=서버명으로 독립 인스턴스 → 그 서버의 자원/메모리/노트를 로컬에서 관리.env에MCP_HTTP_TOKEN설정 후make up-mcp→ 노트북에서 서버별 원격 MCP로 접속 (아래 디바이스/서버별 관리)
③ 팀원 — 이미 떠 있는 서버에 붙기
운영자에게 받은 URL + 토큰을 클라이언트에 원격 MCP로 등록 (아래 원격 MCP)
추론은 그 인스턴스의 계정으로 수행 — 내 코딩 구독(Cursor/Claude/Codex)은 그대로 따로 씀
어떤 경로든 메터드 API 0원, 데이터는 그 인스턴스 로컬에만 둡니다.
📂 바로 따라 할 케이스별 예제는 examples/ — API 대체·임베딩·메모리·second-brain·MCP까지. 👥 내 직군에선 어떻게? → 직군별 유즈케이스 — 개발(백엔드·프론트·앱·게임), 데이터/ML, QA·아키텍트·인프라, 비개발(PM·라이터·보안·연구자), 콘텐츠 크리에이터(AI 글작성·유튜브 대본/편집·썸네일·인플루언서) 등 19개 페르소나.
🧑💻 직군별 워크플로우 — 내 직군, 바로 실행
make up 후 내 직군 스크립트 하나만 돌리면 localmind 활용이 한 번에 체감됩니다. 전부 실행·검증됨.
그룹 | 바로 실행할 워크플로우 |
개발 | |
데이터/ML | |
품질·설계·운영 | |
비개발 | |
콘텐츠 | |
1인 개발 |
각 직군의 상황 → 활용 → 워크플로우 단계 → 효과는 👉 직군별 유즈케이스(19 페르소나). 모든 예제는 examples/.
실제 플로우 예시 — 어떻게 쓰고, 무엇이 쌓이는지
실행 중인 스택에 그대로 태운 두 직군. 응답·저장 데이터 모두 실제 출력입니다.
백엔드 개발자 — 로그 요약 → 분류(perf) → 대응 결정을 노트로 적재 → NOTES_DIR에 .md 정본이 쌓임(이후 검색·RAG 대상)

기획자(PM) — 결정을 remember로 mem0에 저장 → PRD 초안 → 2주 뒤 recall로 의미 회상(결정·이유가 기억으로 쌓임)

동작 방식
OpenAI 형식 요청(
messages,stream, ...)을 받습니다.model필드로 백엔드를 결정합니다 (claude vs codex).messages를 시스템 프롬프트 + 단일 프롬프트로 평탄화해 해당 CLI를-p/exec비대화형 모드로 실행합니다.CLI의 JSON/스트리밍 출력을 OpenAI 형식(
chat.completion/chat.completion.chunkSSE)으로 변환해 돌려줍니다.
CLI는 순수 텍스트 생성기로 동작합니다 — claude는 --tools ""로 모든 내장 도구를 끄고, codex는 -s read-only로 격리하며, 둘 다 임시 디렉토리에서 실행해 프로젝트 설정(CLAUDE.md 등)이 섞이지 않습니다.
요구 사항
Node.js >= 20
로그인된
claudeCLI (claude가 PATH에 있어야 함)로그인된
codexCLI (codex 백엔드를 쓸 경우)
설치 및 실행
make install # 의존성
make dev # 개발 모드(watch, 코드 변경 시 자동 재시작)
make build && npm start # 빌드 후 로컬 실행(비-Docker; npm start만 make 미대상)기본적으로 http://127.0.0.1:8787 에서 대기합니다. 설정은 환경변수로 변경합니다 (.env.example 참고).
Docker
이미지에는 claude·codex CLI가 함께 설치됩니다. 인증은 호스트의 CLI 인증 디렉토리를 볼륨 마운트해 재사용합니다(별도 로그인 불필요).
전제: 호스트에서
claude/codex에 이미 로그인되어 있어야 합니다(~/.claude,~/.codex).
docker compose
docker compose up -d --build # 채팅 API만(프로파일 생략) · 전체 스택은 `make up`
make healthdocker-compose.yml이 호스트의 ~/.claude, ~/.claude.json, ~/.codex를 컨테이너로 마운트합니다.
docker run
docker build -t localmind .
docker run -d --name localmind -p 8787:8787 \
-v "$HOME/.claude:/root/.claude" \
-v "$HOME/.claude.json:/root/.claude.json" \
-v "$HOME/.codex:/root/.codex" \
localmind주의사항
컨테이너 안의 CLI는 호스트와 동일한 계정/인증/상태를 공유합니다. 호스트에서 같은 CLI를 동시에 무겁게 쓰면 상태(히스토리·토큰 갱신 등)가 섞일 수 있습니다.
컨테이너 내부는
HOST=0.0.0.0으로 바인딩해야 외부에서 접근됩니다(이미지 기본값).claude는 glibc 네이티브 바이너리라 베이스 이미지는 Debian 계열(
node:24-slim)을 사용합니다(alpine/musl 비호환).인증 토큰 갱신을 컨테이너가 호스트 파일에 다시 쓰므로 볼륨은 읽기·쓰기로 마운트합니다.
임베딩까지 포함한 완전한 로컬 스택 (gateway 프로파일)
localmind는 채팅(생성)만 다룹니다 — CLI는 임베딩(텍스트→벡터)을 못 하기 때문입니다. 그래서 임베딩이 필요한 소비자(예: supermemory 같은 메모리/RAG 시스템)를 위해, 임베딩 서버(Ollama+bge-m3) 와 통합 게이트웨이(LiteLLM) 를 opt-in 프로파일로 함께 제공합니다.
소비자 ──(base URL 하나)──▶ LiteLLM 게이트웨이 (:4000)
├─ /v1/embeddings → ollama (bge-m3)
└─ /v1/chat/completions → localmind → claude/codex CLI소비자는 base URL 하나(http://<host>:4000/v1)만 바라보면, 임베딩은 로컬 모델로·채팅은 CLI 구독으로 자동 분기됩니다.
기동
docker compose --profile gateway up -d --build # 임베딩만(메모리 제외); 메모리까지 한 번에: make up
# 최초 1회: ollama가 bge-m3(~1.2GB)를 자동 pull (수 분 소요)올라오는 서비스:
서비스 | 포트 | 역할 |
| 4000 | 통합 게이트웨이 (소비자가 바라보는 단일 엔드포인트) |
| 8787 | 채팅 (claude/codex CLI) |
| (내부) | 임베딩 백엔드 (bge-m3) |
소비자(supermemory 등) 연결
OpenAI 호환 클라이언트의 base URL/key만 게이트웨이로 바꿉니다.
OPENAI_BASE_URL=http://<host>:4000/v1
OPENAI_API_KEY=sk-local # LITELLM_MASTER_KEY 값
EMBEDDING_MODEL=text-embedding-3-small # 또는 bge-m3 (둘 다 bge-m3로 매핑됨)from openai import OpenAI
c = OpenAI(base_url="http://localhost:4000/v1", api_key="sk-local")
c.embeddings.create(model="text-embedding-3-small", input="의미 검색용 텍스트") # → ollama bge-m3 (1024차원)
c.chat.completions.create(model="claude-sonnet-4-6", # → localmind → claude
messages=[{"role": "user", "content": "안녕"}])라우팅 규칙 (litellm.config.yaml)
임베딩 모델명(
text-embedding-3-small/large,ada-002,bge-m3) → ollama bge-m3그 외 모든 모델 → localmind (모델명으로 claude/codex 라우팅)
다른 임베딩 모델명을 쓰면
litellm.config.yaml의model_list에 한 줄 추가하세요(없으면 채팅으로 잘못 라우팅됨).
주의
임베딩 모델 일관성: 색인과 쿼리는 같은 임베딩 모델이어야 합니다. 모델을 바꾸면 전체 재색인이 필요합니다(벡터 차원도 bge-m3=1024로 맞출 것).
EMBEDDING_MODEL/LITELLM_MASTER_KEY는.env로 바꿀 수 있습니다.GPU가 있고 대량 색인이면 ollama 대신 HF TEI/Infinity 같은 임베딩 전용 서버로
litellm.config.yaml의api_base만 바꿔도 됩니다.
메모리 서비스 (OpenMemory, memory 프로파일)
게이트웨이 위에 OpenMemory(mem0) 를 얹어, 메터드 API 0원으로 동작하는 메모리/RAG 서비스를 바로 띄울 수 있습니다. OpenMemory의 LLM(사실 추출)·임베더를 모두 게이트웨이로 돌려 claude(또는 codex) + bge-m3로 동작합니다.
OpenMemory(:8767) ──▶ LiteLLM 게이트웨이
│ add/list/search ├─ 임베딩 → ollama(bge-m3)
▼ └─ 추출 LLM → localmind → claude/codex CLI
Postgres + pgvector (메타데이터 + 벡터)OpenMemory는 게시 이미지가 pgvector 미지원 + 읽기 버그가 있어, 최신 소스를 빌드하고 localmind 패치(
openmemory/)를 적용합니다. 저장소는 Postgres+pgvector 단일 DB로 통합합니다.
기동
make up # = docker compose --profile gateway --profile memory up -d --build
# 최초 1회: OpenMemory 소스 빌드 + bge-m3(~1.2GB) pull (수 분)
# openmemory-init 사이드카가 자동으로:
# - pgvector 테이블을 임베딩 차원(bge-m3=1024)에 맞춰 선생성
# - LLM/임베더 모델을 게이트웨이 라우팅에 맞게 설정올라오는 서비스: openmemory(:8767 REST), mem0_pg(Postgres+pgvector), openmemory-init(일회성 부트스트랩) + gateway 일체.
사용 (REST)
# 추가: claude가 사실을 추출해 저장 — user_id는 OPENMEMORY_USER 값
curl -X POST http://localhost:8767/api/v1/memories/ \
-H "Content-Type: application/json" \
-d '{"user_id":"localmind","text":"내 강아지 초코는 오이를 간식으로 좋아한다.","infer":true}'
# 목록 (키워드+최신순)
curl "http://localhost:8767/api/v1/memories/?user_id=localmind&size=20"
# 검색 (키워드 필터)
curl -X POST http://localhost:8767/api/v1/memories/filter \
-H "Content-Type: application/json" \
-d '{"user_id":"localmind","search_query":"강아지","size":10}'의미 기반 회상(임베딩 벡터 검색)은 mem0 엔진으로:
docker exec localmind-openmemory python -c "
from app.utils.memory import get_memory_client
print(get_memory_client().search('반려견 간식', filters={'user_id':'localmind'}, limit=3))"
# → 의미 매칭 결과 (점수 높은 순)검증된 동작 / 한계
✅ 쓰기: REST 추가 → claude 사실 추출 → bge-m3 임베딩(1024) → pgvector 저장.
✅ 읽기:
GET /memories(목록)·POST /memories/filter(검색) 정상 동작 (소스 패치로 업스트림 읽기 버그 해소).✅ 의미 회상: mem0 엔진
search()가 게이트웨이로 임베딩해 의미 기반 검색.user_id는OPENMEMORY_USER로 시드된 사용자여야 합니다(임의 id는 "User not found").자동 카테고리화(categorization)는 OpenAI 구조화 출력(json_schema)을 요구하는데 CLI 경로에선 강제가 안 되고 재시도가 워커를 막아 비활성화했습니다(메모리 기능엔 영향 없음).
mem0 사실 추출 프롬프트는 기본이 영어("User owns ...")라,
openmemory/patch.py에서 입력과 같은 언어 + 주어 생략으로 패치했습니다 → 한국어 입력은 "매일 아침 6시에 기상한다"처럼 자연스러운 한국어로 저장됩니다.OpenMemory는 단일 워커라 동시 add가 몰리면 직후 요청이 잠깐 밀릴 수 있습니다.
임베딩 모델을 바꾸면
EMBEDDING_DIMS도 맞추고make clean(볼륨 삭제)으로 초기화하세요(차원이 테이블에 고정됨).
메모리 백업 (git)
mem0 메모리는 Postgres에 있어 그대로는 git-native가 아닙니다. 마크다운으로 export해 git에 올려 백업합니다(메모리 1개당 한 줄 → diff가 깔끔). 노트(.md)는 이미 파일이라 그 자체로 git 백업되니, 이 둘이면 두뇌 전체가 git에 들어갑니다.
# 내보내기 (마크다운)
OPENMEMORY_USER=내이름 make memory-export FILE=~/brain/memory.md
# 백업: 노트 폴더(또는 백업 repo)에서 커밋·푸시
cd ~/brain && git add memory.md && git commit -m "memory backup" && git push
# 복원 (멱등 — 이미 있는 내용은 스킵)
OPENMEMORY_USER=내이름 make memory-import FILE=~/brain/memory.mdexport 출력은
- <사실>한 줄씩이라 메모리 추가/삭제가 git diff에 1줄로 보입니다.import는
infer:false로 사실을 그대로 저장하고, 이미 있는 내용은 건너뜁니다(여러 번 실행 안전).파생물(pgvector/
.brain-index.json)은 백업 불필요 — 노트·메모리에서 재생성됩니다.
MCP 서버 (도구로 사용)
localmind의 능력을 MCP 도구로 노출해, MCP 호스트(Claude Desktop / Cursor / Cline 등)가 자기 모델로 돌면서 끌어 쓰게 합니다. (MCP는 호스트의 모델을 바꾸는 게 아니라 도구를 줍니다.)
도구 | 설명 |
| 이 인스턴스(디바이스/서버) 식별 — 어느 서버의 메모리/노트인지 |
| claude/codex CLI에 교차 질의(다른 모델 상담) → localmind 경유 |
| 진화하는 기억에 사실 저장 (mem0: claude 추출 + bge-m3) |
| 의미 기반 회상 (mem0 벡터 검색) |
| second-brain: 마크다운 노트 저장 + 인덱싱 (정본은 |
| second-brain: 내 노트 의미검색(원문·경로) |
| second-brain: 내 노트만 근거로 RAG 답변(출처 인용) |
두 종류의 기억:
remember/recall은 mem0의 진화하는 사실 메모리,capture_note/search_notes/ask_brain은 내 마크다운 노트(정본) RAG 입니다.
MCP 호스트(Claude Desktop/Cursor/Cline)
│ stdio
▼
localmind MCP 서버 (dist/mcp.js)
├─ ask → localmind :8787 (claude/codex)
└─ remember/recall → OpenMemory :8767 (pgvector)준비
make install build # dist/mcp.js 생성
make up # 스택 기동(게이트웨이+메모리)
# - ask는 gateway 스택(:8787)만 있으면 동작
# - remember/recall은 memory 스택(:8767)도 필요호스트 설정 (stdio)
Claude Desktop claude_desktop_config.json (Cursor .cursor/mcp.json, Cline MCP 설정도 동일 구조):
{
"mcpServers": {
"localmind": {
"command": "node",
"args": ["/절대경로/localmind/dist/mcp.js"],
"env": { "OPENMEMORY_USER": "내이름" }
}
}
}원격 MCP (HTTP/SSE) — URL 하나로 팀/원격 접속
stdio는 각자 로컬 서브프로세스라 "URL 공유"가 안 됩니다. 원격 서버(dist/mcp-http.js)는
URL 하나로 Claude Code / Cursor / ChatGPT 원격 connector 까지 접속하게 합니다.
# .env 에 MCP_HTTP_TOKEN(필수) + MCP_INSTANCE 설정 후
make up-mcp # = docker compose --profile gateway --profile memory --profile mcp up -d --build
# → http://<host>:8788/mcp (Streamable HTTP), http://<host>:8788/sse (레거시)POST /mcp— Streamable HTTP(현대 표준),GET /sse+POST /messages— 레거시 SSE,GET /health.인증 필수: 네트워크 노출이므로
MCP_HTTP_TOKEN없으면 기동 거부. 클라이언트는Authorization: Bearer <토큰>.tailnet 등 신뢰 네트워크에만 노출하세요(포트 8788).
클라이언트 등록 예:
# Claude Code
claude mcp add --transport http my-server https://<host>:8788/mcp --header "Authorization: Bearer <토큰>"// Cursor .cursor/mcp.json
{ "mcpServers": { "my-server": {
"url": "http://<host>:8788/mcp",
"headers": { "Authorization": "Bearer <토큰>" }
}}}디바이스/서버별 관리 (인프라 운영)
한 인스턴스 = 한 디바이스/서버. 서버마다 MCP_INSTANCE를 다르게 주면 그 서버의
메모리·노트가 인스턴스별로 격리됩니다. 각 서버가 자기 자원 정보(스펙·설정·장애 이력)를
로컬에서 관리하고, 클라이언트에선 서버별 원격 MCP를 각각 등록해 골라 씁니다.
db-server : MCP_INSTANCE=db-server → db-server의 메모리/노트 (http://db-server:8788/mcp)
app-server-1: MCP_INSTANCE=app-server-1 → app-server-1의 메모리/노트각 인스턴스는 독립: 그 서버의 localmind + 그 서버의 CLI 로그인을 쓰므로, 한 서버가 다른 서버의 계정/스택에 의존하지 않습니다(중앙 의존 0).
메모리 격리:
OPENMEMORY_USER를 안 주면MCP_INSTANCE가 곧 메모리 owner가 됩니다.whoami도구로 "지금 어느 서버의 두뇌인지" 즉시 확인.각 서버에서
capture_note로 그 서버의 자원/장애를 기록 →ask_brain으로 그 서버 한정 RAG.
환경변수 (MCP 서버)
변수 | 기본값 | 설명 |
| 호스트명 | 디바이스/서버 식별자(메모리 owner 기본값) — 서버별 격리 |
| (없음) | 원격 MCP Bearer 토큰. 네트워크 노출 시 필수 |
|
| 원격 MCP 바인딩 |
|
| ask가 호출할 localmind |
| (없음) | localmind 인증 시 |
|
| remember/recall 대상 |
|
| 메모리 소유자 id |
|
| ask / ask_brain 기본 모델 |
|
| second-brain 노트 폴더(정본). 내 노트 경로로 지정 |
|
| 임베딩 인덱스 위치. git/싱크 볼트를 안 더럽히려면 밖으로 |
|
| 노트 임베딩(게이트웨이) |
|
| (게이트웨이가 bge-m3로 매핑) |
|
| 인덱싱 튜닝 |
인덱싱 성능: 첫 인덱싱은 노트 전체를 임베딩하므로 시간이 걸리고(증분·resumable이라 이후엔 변경분만), bge-m3 CPU 임베딩이 바닥입니다. 더 빠르게:
bge-m3 그대로 + GPU/전용 임베딩 서버(TEI/Infinity) 로
EMBEDDINGS_URL교체 (한국어 품질 유지하며 가속, 권장)더 가벼운 모델로 바꾸려면 반드시 다국어 모델(multilingual-e5, snowflake-arctic-embed2)을 —
nomic-embed-text등 영어 전용 모델은 한국어 검색 품질이 크게 떨어집니다.모델 변경 시 벡터 차원이 달라지므로
EMBEDDING_DIMS조정 + 재인덱싱 필요.
검증: make smoke(MCP 포함 일괄) 또는 원격 MCP는 npm run smoke:mcp:http.
사용 예시
🧩 케이스별 runnable 예제 모음 → examples/ — API 대체(Python/Node/Anthropic), 함수호출, 임베딩·의미검색, 메모리, second-brain, LangChain, MCP(Cursor/Claude/Codex·원격 인프라), 역할별 시나리오. 전부 실행·검증됨.
curl
curl http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "sonnet",
"messages": [{"role": "user", "content": "안녕하세요"}]
}'OpenAI Python SDK
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8787/v1",
api_key="not-needed", # LOCALMIND_API_KEY를 설정했다면 그 값
)
resp = client.chat.completions.create(
model="sonnet", # → claude 백엔드
messages=[{"role": "user", "content": "한 줄로 자기소개 해줘"}],
)
print(resp.choices[0].message.content)
# 스트리밍
for chunk in client.chat.completions.create(
model="gpt-5.5", # → codex 백엔드
messages=[{"role": "user", "content": "1부터 5까지 세줘"}],
stream=True,
):
print(chunk.choices[0].delta.content or "", end="")OpenAI Node SDK
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://127.0.0.1:8787/v1", apiKey: "not-needed" });
const res = await client.chat.completions.create({
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: "hello" }],
});Anthropic SDK (/v1/messages)
OpenAI뿐 아니라 공식 Anthropic SDK도 그대로 붙습니다. base_url만 localmind로 바꾸면 됩니다.
from anthropic import Anthropic
client = Anthropic(
base_url="http://127.0.0.1:8787",
api_key="not-needed", # LOCALMIND_API_KEY를 설정했다면 그 값 (x-api-key 헤더로 전송됨)
)
msg = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="간결하게 답해줘",
messages=[{"role": "user", "content": "한 줄로 자기소개 해줘"}],
)
print(msg.content[0].text)
# 스트리밍
with client.messages.stream(
model="sonnet",
max_tokens=256,
messages=[{"role": "user", "content": "1부터 5까지 세줘"}],
) as stream:
for text in stream.text_stream:
print(text, end="")
model에codex:gpt-5.5처럼 지정하면 Anthropic 포맷으로 codex 백엔드를 호출할 수도 있습니다.
모델 라우팅
model 필드로 백엔드와 실제 모델을 결정합니다.
입력 | 백엔드 | CLI 모델 |
| claude | 그대로 전달 |
| codex | 그대로 전달 |
| claude (강제) |
|
| codex (강제) |
|
| 패턴 매칭 | 프리픽스 제거 후 전달 |
그 외/빈 값 |
| 백엔드 기본 모델 |
codex의 사용 가능 모델은 로그인 계정에 따라 다릅니다. 계정이 지원하지 않는 모델을 지정하면 400 오류가 반환됩니다. 기본값은
~/.codex/config.toml의 모델과 맞춰 두는 것을 권장합니다.
엔드포인트
메서드 | 경로 | 포맷 | 설명 |
POST |
| OpenAI | 채팅 완성 (스트리밍/비스트리밍) |
POST |
| Anthropic | 메시지 (스트리밍/비스트리밍) |
GET |
| OpenAI | 모델 목록 |
GET |
| — | 헬스체크 (인증 불필요) |
인증(LOCALMIND_API_KEY 설정 시)은 OpenAI식 Authorization: Bearer <키> 와 Anthropic식 x-api-key: <키> 헤더를 모두 허용합니다.
세션 영속화 (컨텍스트 유지)
OpenAI/Anthropic API는 stateless라 보통 매 요청마다 전체 대화 히스토리를 다시 보냅니다. localmind는 대화를 CLI 세션에 매핑해, 이어지는 요청에서는 claude --resume / codex exec resume로 새 턴만 전송합니다. 결과적으로 CLI 측 컨텍스트와 프롬프트 캐시를 활용해 토큰을 아낍니다.
SESSION_MODE로 동작을 정합니다.
모드 | 동작 |
| 메시지 prefix를 해시로 자동 인식. 클라이언트 코드 변경 불필요 — 일반적인 "히스토리를 계속 append하는" 채팅이면 자동으로 이어집니다. |
|
|
| 세션 없이 항상 전체 히스토리 전송. |
# explicit 모드 예: 같은 세션 id로 요청하면 컨텍스트가 이어짐
curl http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-localmind-session: my-convo-1" \
-d '{"model":"sonnet","messages":[{"role":"user","content":"내 이름은 지훈이야"}]}'auto 모드 주의점
클라이언트가 우리가 준 assistant 응답을 그대로 다시 보낼 때 prefix가 일치해 이어집니다. 응답을 편집/요약해서 보내면 매칭이 깨지고, 이 경우 안전하게 전체 히스토리로 새 세션을 다시 만듭니다(틀린 답이 나오지는 않고, 토큰 절약만 사라짐).
재생성(regeneration)·분기 안전: 같은 prefix가 두 번 오면 첫 번째만 resume하고(consume-once), 두 번째는 fresh로 복구해 세션 오염을 막습니다.
세션 매핑은 인메모리이며 서버 재시작 시 사라집니다(
SESSION_TTL_MS경과 시에도 만료). 그래도 컨텍스트는 항상 클라이언트가 보낸 히스토리로 복구 가능합니다.
함수 호출 (Function Calling, A2 프롬프트 방식 PoC)
OpenAI(/v1/chat/completions)와 Anthropic(/v1/messages) 양쪽 모두 함수 호출을 지원합니다. 모델이 함수 호출이 필요하다고 판단하면 각 포맷의 표준 응답(OpenAI tool_calls / Anthropic tool_use 블록)을 돌려줍니다. 공식 OpenAI/Anthropic SDK의 함수 호출 흐름이 그대로 동작합니다.
OpenAI (/v1/chat/completions)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}]
# 1) 모델이 도구 호출을 결정
r = client.chat.completions.create(model="sonnet", tools=tools,
messages=[{"role": "user", "content": "서울 날씨 알려줘"}])
call = r.choices[0].message.tool_calls[0] # get_weather({"city":"서울"})
# 2) 함수를 실행하고 결과를 다시 전달 → 모델이 최종 답변
r2 = client.chat.completions.create(model="sonnet", tools=tools, messages=[
{"role": "user", "content": "서울 날씨 알려줘"},
r.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": "맑음, 25도"},
])
print(r2.choices[0].message.content)Anthropic (/v1/messages)
Anthropic 포맷(tools는 {name, description, input_schema}, 결과는 user 메시지의 tool_result 블록)도 그대로 지원합니다.
tools = [{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}]
r = client.messages.create(model="sonnet", max_tokens=1024, tools=tools,
messages=[{"role": "user", "content": "서울 날씨 알려줘"}])
tool_use = next(b for b in r.content if b.type == "tool_use") # get_weather(input={"city":"서울"})
r2 = client.messages.create(model="sonnet", max_tokens=1024, tools=tools, messages=[
{"role": "user", "content": "서울 날씨 알려줘"},
{"role": "assistant", "content": r.content},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": tool_use.id, "content": "맑음, 25도"}]},
])
print(r2.content[0].text)작동 방식 (A2): CLI에는 "외부가 실행할 함수 스펙을 받아 호출만 내뱉고 멈추는" 모드가 없으므로, tools 스펙을 시스템 프롬프트에 주입하고 모델이 약속된 JSON({"tool_calls":[...]})으로 출력하게 한 뒤 그 텍스트를 파싱해 각 포맷의 tool_calls/tool_use로 변환합니다.
한계 (PoC)
프롬프트 기반이라 100% 보장은 아님: 모델이 형식을 벗어나면 도구 호출로 인식되지 않고 일반 텍스트로 처리됩니다.
스트리밍 시 버퍼링: 도구 호출 여부는 전체 출력을 봐야 알 수 있어,
tools가 있으면 전체를 모은 뒤 한 번에 방출합니다(토큰 단위 스트리밍 아님).tools가 없으면 기존대로 실시간 스트리밍.백엔드 특성 차이: claude는
--tools ""로 순수 텍스트화되어 주입한 도구 결과를 충실히 따릅니다. codex는 더 에이전트적이라 자체 도구(웹 검색 등)를 써서 주입한 결과와 다른 답을 낼 수 있습니다.멀티스텝 도구 루프에서 컨텍스트 유지는 세션 영속화를 따릅니다(매칭 안 되면 전체 히스토리로 복구).
설정 (환경변수)
변수 | 기본값 | 설명 |
|
| 포트 |
|
| 바인딩 호스트 |
| (없음) | 설정 시 |
|
| 라우팅 실패 시 기본 백엔드 |
|
| claude 기본 모델 |
|
| codex 기본 모델 |
|
| claude 실행 파일 경로 |
|
| codex 실행 파일 경로 |
|
| 요청 타임아웃(ms) |
|
| 세션 영속화 모드 ( |
|
| 세션 매핑 보관 시간(ms) |
|
| 세션 매핑 최대 개수 |
|
|
|
검증
기본 묶음은 make smoke(API+MCP+brain). 아래는 백엔드·엔드포인트별 세분 변형으로, make 타겟이 없어 npm run을 직접 씁니다.
make dev # 다른 터미널에서 서버 실행
# OpenAI 엔드포인트(/v1/chat/completions)
MODEL=sonnet npm run smoke # claude 백엔드
MODEL=gpt-5.5 npm run smoke # codex 백엔드
# Anthropic 엔드포인트(/v1/messages)
MODEL=sonnet npm run smoke:anthropic # claude 백엔드
MODEL=codex:gpt-5.5 npm run smoke:anthropic # codex 백엔드
# 함수 호출
MODEL=sonnet npm run smoke:tools # OpenAI tools
MODEL=sonnet npm run smoke:anthropic:tools # Anthropic tool_use문제 해결 (Troubleshooting)
증상 | 원인 / 해결 |
채팅 호출이 실패/빈 응답 | 호스트에 |
|
|
| 첫 기동은 모델 pull(bge-m3 ~1.2GB) + OpenMemory 소스 빌드로 수 분 걸림 → |
포트 충돌(8787/4000/8767/8788) | 이미 쓰는 포트면 |
메모리 |
|
노트 첫 인덱싱이 느림 | bge-m3 CPU 임베딩이 바닥(이후 증분이라 빠름). 급하면 GPU/TEI로 |
임베딩 모델 변경 후 차원 오류 |
|
CI
GitHub Actions(.github/workflows/ci.yml)에서 push/PR마다 다음을 검증합니다.
typecheck & build: Node 20·22·24 매트릭스로
npm ci → typecheck → builddocker build: 이미지가 정상 빌드되는지 확인
스모크 테스트(
npm run smoke*)는 인증된claude/codexCLI가 필요해 CI에서는 실행하지 않습니다. 로컬에서 서버를 띄운 뒤 수동으로 돌리세요.
현재 제한 사항 (MVP)
Function calling / tools: OpenAI·Anthropic 양쪽 엔드포인트에서 A2 프롬프트 방식 PoC로 지원.
멀티모달: 이미지 등 비텍스트 입력은 자리표시자로 치환됩니다.
대화 맥락: 세션 영속화(위 참고)로 CLI 세션을 resume합니다. 매칭이 안 되거나
SESSION_MODE=off면 멀티턴messages를User:/Assistant:라벨로 평탄화해 전체 전달합니다.토큰 수: CLI가 보고하는 값을 그대로 전달하므로, CLI 내부 시스템 프롬프트 토큰이
prompt_tokens(input_tokens)에 포함됩니다.temperature,max_tokens등 일부 샘플링 파라미터는 CLI가 지원하지 않아 무시될 수 있습니다.Anthropic 스트리밍:
input_tokens는 스트림 시작 시점(message_start)에 0으로 보내고, 최종message_delta에서 실제 값을 채웁니다(SDK가 이를 합산해 최종 usage를 계산).
데모
실제 실행 중인 스택의 세션입니다 — make health → OpenAI 호환 chat(claude) → 로컬 임베딩(bge-m3). 전부 메터드 API 0원, 완전 로컬. (응답·임베딩 값 모두 실제 출력)

직접 보려면
make up후 위 명령들을 그대로 실행하면 됩니다.
라이선스
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.
Latest Blog Posts
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/shaul1991/localmind'
If you have feedback or need assistance with the MCP directory API, please join our Discord server