
요즘 보이는 Context Mode / MCP / Claude mem / caveman 스타일 전부 하나의 흐름입니다.
❌ 토큰을 줄인다
✅ LLM이 불필요한 데이터를 “안 보게 만든다”
왜 이게 유행인가?
AI 코딩 에이전트 쓰면 바로 겪습니다.
- 세션 길어지면 느려짐
- 맥락 깨짐
- 비용 증가
- 대용량 데이터 처리 불가
이유는 단순합니다.
LLM = 입력된 모든 텍스트를 다 읽는다
[Raw Data] → [Sandbox / DB 저장]
↓
[검색 / 코드 실행]
↓
[결과만 LLM 전달]
핵심
“LLM은 처리하지 말고, 결과만 받아라”
토큰 절약 4대 전략
① 압축 (Compaction)
Before
대화 전체 계속 누적
After
Session Summary:
- 로그 분석 수행
- timeout 에러 발견
- 사용자 재분석 요청
👉 효과
- 세션 유지 (30분 → 수시간)
- 토큰 감소
👉 위험
- 과도한 요약 → 정보 손실
② 검색 기반 구조 (FTS5 + BM25)
“전체를 넣지 말고 필요한 것만 검색”
SQLite + FTS5 구현
CREATE VIRTUAL TABLE events USING fts5(content);
INSERT INTO events(content)
VALUES ('user edited file'), ('error timeout occurred');
SELECT content
FROM events
WHERE events MATCH 'error'
ORDER BY bm25(events)
LIMIT 5;
👉 핵심
“세션을 DB로 만들고 검색한다”
③ 실행 분리 (Execution Isolation)
❌ 잘못된 방식
코드 전체 붙여넣고 "함수 몇 개?"
✅ 올바른 방식
import re
with open("app.py") as f:
code = f.read()
print(len(re.findall(r"def ", code)))
추가 예시
# 로그 분석
grep -i error app.log | tail -n 200
# JSON 분석
jq '.users | length' data.json
# Git diff
git diff HEAD~1 | head -n 200
👉 효과
- 토큰 90% 절감
- 정확도 상승
- hallucination 감소
④ 출력 압축 (Output Compression)
❌ 장황한 출력
The issue appears to be related to a timeout error...
✅ 압축 출력
- error: timeout
- cause: db connection
- fix: increase pool
👉 효과
- 출력 토큰 65~75% 감소
MCP 아키텍처 완전 이해
MCP 구조
Host (LLM)
↓
Client
↓
MCP Server
├─ Tools
├─ Resources
└─ Prompts
핵심 역할
컨텍스트 외부화
- SQLite 저장
- 파일 시스템
- 캐시
이벤트 기반 관리
CREATE TABLE events (
id INTEGER PRIMARY KEY,
type TEXT,
content TEXT,
created_at DATETIME
);
검색 기반 복구
SELECT * FROM events
WHERE content MATCH 'error'
ORDER BY bm25(events)
LIMIT 10;
실행 분리
ctx_execute("grep error app.log")
👉 결론
MCP = “LLM을 DB + OS처럼 쓰는 구조”
ctx_* MCP 도구 전체 구조
| 도구 | 설명 |
|---|---|
| ctx_execute | 단일 명령 실행 |
| ctx_batch_execute | 다중 작업 |
| ctx_execute_file | 파일 기반 실행 |
| ctx_index | 데이터 저장 |
| ctx_search | 검색 |
| ctx_fetch_and_index | URL → 저장 |
| ctx_stats | 상태 확인 |
| ctx_doctor | 문제 진단 |
| ctx_upgrade | 업데이트 |
| ctx_purge | 데이터 삭제 |
| ctx_insight | 분석 |
👉 이건 사실상
“LLM 전용 운영체제”
Hook 기반 제어
Hook 종류
- PreToolUse
- PostToolUse
- SessionStart
- PreCompact
예시
def pre_tool_use(cmd):
if "rm -rf" in cmd:
raise Exception("blocked")
def pre_compact(context):
return summarize(context)
👉 의미
“언제 실행하고, 언제 압축할지 제어”
세션 연속성 (Session Continuity)
구조
- 이벤트 저장
- 요약 저장
- 검색
- 재조합
기존 방식
대화 계속 유지
MCP 방식
필요한 부분만 재구성
👉 효과
- 세션 30분 → 3시간 이상 가능
- 메모리 문제 해결
플랫폼별 차이
| 플랫폼 | 특징 |
|---|---|
| Claude Code | Hook 강력 |
| Cursor | 제한적 |
| Codex CLI | 자유도 높음 |
| Gemini CLI | 통합형 |
👉 핵심
“같은 MCP라도 기능 차이 있음”
보안 관점
위험 요소
명령 실행
ctx_execute("rm -rf /")
외부 fetch
ctx_fetch_and_index("http://malicious.site")
자동 승인
보안 가이드
allow / deny 정책
deny:
- "rm -rf *"
- "curl * | bash"
allow:
- "grep *"
- "jq *"
샌드박스
- container
- read-only FS
로그 기록
audit.log
사용자 승인
👉 핵심
“토큰 절약보다 실행 통제가 더 중요”
운영 관점
✔ 로컬 실행
- telemetry 없음
- 데이터 외부 유출 없음
✔ SQLite 기반
- 가볍고 빠름
- FTS5 검색 가능
✔ License
- Elastic License 2.0
- SaaS 제공 제한
✔ 관리 도구
- ctx_stats → 상태
- ctx_doctor → 문제 진단
- ctx_purge → 데이터 관리
바로 따라해보기 (미니 구현)
SQLite + FTS5
sqlite3 ctx.db
CREATE VIRTUAL TABLE events USING fts5(content);
INSERT INTO events VALUES ('error timeout'), ('file updated');
Python 검색
import sqlite3
conn = sqlite3.connect("ctx.db")
cursor = conn.cursor()
cursor.execute("""
SELECT content FROM events
WHERE events MATCH 'error'
ORDER BY bm25(events)
LIMIT 5;
""")
print(cursor.fetchall())
실행 분리 적용
grep error app.log > result.txt
→ 결과만 LLM에 전달
가장 중요한 3가지
1️⃣ LLM에 원본 데이터를 주지 말 것
2️⃣ 코드/명령으로 먼저 처리할 것
3️⃣ 필요한 결과만 전달할 것
“토큰 절약은 기술이 아니라 아키텍처다”
추천 구조
실제로는 이렇게 나누는 게 제일 편합니다.
ctx-mode/
├─ store.py # SQLite + FTS5 저장/검색 로직
├─ mcp_server.py # MCP tools
├─ api.py # FastAPI 운영용 API
└─ requirements.txt
이렇게 분리하면 좋은 점은 세 가지입니다.
- MCP 호스트(Claude Code, Cursor 등)는
mcp_server.py만 붙이면 됩니다. - 운영팀은
api.py로 상태 확인, 검색, purge를 할 수 있습니다. - 둘 다 같은 SQLite 파일을 보므로 세션 이력이 한 곳에 모입니다.
requirements.txt
fastapi
uvicorn[standard]
mcp
pydantic
store.py — SQLite + FTS5 핵심 저장소
from __future__ import annotations
import json
import sqlite3
import threading
from pathlib import Path
from typing import Any
DEFAULT_DB_PATH = Path("~/.ctx_mode/ctx.db").expanduser()
class SQLiteStore:
def __init__(self, db_path: Path | str = DEFAULT_DB_PATH):
self.db_path = Path(db_path).expanduser()
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self.lock = threading.Lock()
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row
# 운영 편의용 기본 설정
self.conn.execute("PRAGMA journal_mode=WAL;")
self.conn.execute("PRAGMA synchronous=NORMAL;")
self.conn.execute("PRAGMA foreign_keys=ON;")
self.init_db()
def init_db(self) -> None:
with self.lock:
self.conn.executescript(
"""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
content TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'manual',
meta_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_events_created_at
ON events(created_at);
CREATE TABLE IF NOT EXISTS session_summary (
id INTEGER PRIMARY KEY CHECK (id = 1),
summary TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO session_summary(id, summary)
VALUES (1, '');
CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
content,
event_type,
source,
content='events',
content_rowid='id',
tokenize='unicode61'
);
CREATE TRIGGER IF NOT EXISTS events_ai AFTER INSERT ON events BEGIN
INSERT INTO events_fts(rowid, content, event_type, source)
VALUES (new.id, new.content, new.event_type, new.source);
END;
CREATE TRIGGER IF NOT EXISTS events_ad AFTER DELETE ON events BEGIN
INSERT INTO events_fts(events_fts, rowid, content, event_type, source)
VALUES ('delete', old.id, old.content, old.event_type, old.source);
END;
CREATE TRIGGER IF NOT EXISTS events_au AFTER UPDATE ON events BEGIN
INSERT INTO events_fts(events_fts, rowid, content, event_type, source)
VALUES ('delete', old.id, old.content, old.event_type, old.source);
INSERT INTO events_fts(rowid, content, event_type, source)
VALUES (new.id, new.content, new.event_type, new.source);
END;
"""
)
self.conn.commit()
def add_event(
self,
content: str,
event_type: str = "note",
source: str = "manual",
meta: dict[str, Any] | None = None,
) -> dict[str, Any]:
meta_json = json.dumps(meta or {}, ensure_ascii=False)
with self.lock:
cur = self.conn.execute(
"""
INSERT INTO events (event_type, content, source, meta_json)
VALUES (?, ?, ?, ?)
""",
(event_type, content, source, meta_json),
)
self.conn.commit()
row = self.conn.execute(
"""
SELECT id, event_type, content, source, meta_json, created_at
FROM events
WHERE id = ?
""",
(cur.lastrowid,),
).fetchone()
return self._row_to_dict(row)
def search(self, query: str, limit: int = 10) -> list[dict[str, Any]]:
sql = """
SELECT
e.id,
e.event_type,
e.content,
e.source,
e.meta_json,
e.created_at,
bm25(events_fts) AS score,
snippet(events_fts, 0, '[', ']', '…', 12) AS snippet
FROM events_fts
JOIN events e ON e.id = events_fts.rowid
WHERE events_fts MATCH ?
ORDER BY score
LIMIT ?;
"""
with self.lock:
try:
rows = self.conn.execute(sql, (query, limit)).fetchall()
except sqlite3.OperationalError:
# FTS 구문이 깨질 때를 대비한 단순 검색 fallback
fallback_query = '"' + query.replace('"', '""') + '"'
rows = self.conn.execute(sql, (fallback_query, limit)).fetchall()
return [self._row_to_dict(row) for row in rows]
def list_recent(self, limit: int = 20) -> list[dict[str, Any]]:
with self.lock:
rows = self.conn.execute(
"""
SELECT id, event_type, content, source, meta_json, created_at
FROM events
ORDER BY id DESC
LIMIT ?
""",
(limit,),
).fetchall()
return [self._row_to_dict(row) for row in rows]
def stats(self) -> dict[str, Any]:
with self.lock:
total = self.conn.execute("SELECT COUNT(*) FROM events").fetchone()[0]
recent_24h = self.conn.execute(
"""
SELECT COUNT(*)
FROM events
WHERE created_at >= datetime('now', '-1 day')
"""
).fetchone()[0]
by_type_rows = self.conn.execute(
"""
SELECT event_type, COUNT(*) AS cnt
FROM events
GROUP BY event_type
ORDER BY cnt DESC, event_type ASC
"""
).fetchall()
return {
"db_path": str(self.db_path),
"total_events": total,
"events_last_24h": recent_24h,
"by_type": [
{"event_type": row["event_type"], "count": row["cnt"]}
for row in by_type_rows
],
}
def get_summary(self) -> dict[str, Any]:
with self.lock:
row = self.conn.execute(
"SELECT summary, updated_at FROM session_summary WHERE id = 1"
).fetchone()
return {"summary": row["summary"], "updated_at": row["updated_at"]}
def set_summary(self, summary: str) -> dict[str, Any]:
with self.lock:
self.conn.execute(
"""
UPDATE session_summary
SET summary = ?, updated_at = datetime('now')
WHERE id = 1
""",
(summary,),
)
self.conn.commit()
return self.get_summary()
def purge_older_than_days(self, days: int) -> dict[str, Any]:
with self.lock:
before = self.conn.execute("SELECT COUNT(*) FROM events").fetchone()[0]
self.conn.execute(
"DELETE FROM events WHERE created_at < datetime('now', ?)",
(f"-{days} days",),
)
self.conn.commit()
after = self.conn.execute("SELECT COUNT(*) FROM events").fetchone()[0]
return {"deleted": before - after, "remaining": after}
def doctor(self) -> dict[str, Any]:
with self.lock:
version = self.conn.execute("SELECT sqlite_version()").fetchone()[0]
journal_mode = self.conn.execute("PRAGMA journal_mode").fetchone()[0]
return {
"sqlite_version": version,
"journal_mode": journal_mode,
"db_path": str(self.db_path),
"fts5_ready": True,
}
@staticmethod
def _row_to_dict(row: sqlite3.Row | None) -> dict[str, Any]:
if row is None:
return {}
data = dict(row)
if "meta_json" in data:
try:
data["meta"] = json.loads(data["meta_json"] or "{}")
except json.JSONDecodeError:
data["meta"] = {}
return data
mcp_server.py — 실제 MCP 서버
FastMCP는 tool 정의를 함수 시그니처와 docstring으로 자동 생성해 주기 때문에, 이 부분이 제일 깔끔합니다. 공식 예제도 같은 방식으로 tool/resource/prompt를 정의하고 있습니다.
from __future__ import annotations
import os
from typing import Any
from mcp.server.fastmcp import FastMCP
from store import SQLiteStore
store = SQLiteStore()
mcp = FastMCP("ctx-mode", json_response=True)
@mcp.tool()
def ctx_index(
content: str,
event_type: str = "note",
source: str = "manual",
meta: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""세션 이벤트나 지식 조각을 SQLite + FTS5에 저장한다."""
return store.add_event(
content=content,
event_type=event_type,
source=source,
meta=meta,
)
@mcp.tool()
def ctx_search(query: str, limit: int = 10) -> dict[str, Any]:
"""FTS5로 저장된 이벤트를 검색한다."""
return {"query": query, "limit": limit, "results": store.search(query, limit)}
@mcp.tool()
def ctx_recent(limit: int = 20) -> dict[str, Any]:
"""가장 최근 이벤트를 가져온다."""
return {"limit": limit, "results": store.list_recent(limit)}
@mcp.tool()
def ctx_stats() -> dict[str, Any]:
"""DB 상태와 이벤트 통계를 보여준다."""
return store.stats()
@mcp.tool()
def ctx_get_summary() -> dict[str, Any]:
"""세션 요약을 읽는다."""
return store.get_summary()
@mcp.tool()
def ctx_set_summary(summary: str) -> dict[str, Any]:
"""세션 요약을 저장한다."""
return store.set_summary(summary)
@mcp.tool()
def ctx_purge(days: int = 30) -> dict[str, Any]:
"""오래된 이벤트를 삭제한다."""
return store.purge_older_than_days(days)
@mcp.tool()
def ctx_doctor() -> dict[str, Any]:
"""SQLite/FTS5 상태를 점검한다."""
return store.doctor()
def main() -> None:
transport = os.getenv("MCP_TRANSPORT", "stdio").strip().lower()
if transport == "streamable-http":
mcp.run(transport="streamable-http")
else:
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
api.py — FastAPI 운영용 API
FastAPI는 API 서버를 빠르게 만들기에 적합합니다. 공식 문서도 고성능, 타입 힌트 기반 개발을 강조합니다.
from __future__ import annotations
from typing import Any
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from store import SQLiteStore
store = SQLiteStore()
app = FastAPI(title="Context Mode API", version="1.0.0")
class IndexRequest(BaseModel):
content: str = Field(min_length=1)
event_type: str = "note"
source: str = "manual"
meta: dict[str, Any] = Field(default_factory=dict)
class SearchRequest(BaseModel):
query: str = Field(min_length=1)
limit: int = Field(default=10, ge=1, le=100)
class PurgeRequest(BaseModel):
days: int = Field(default=30, ge=1, le=3650)
@app.get("/healthz")
def healthz() -> dict[str, Any]:
return {"ok": True, "doctor": store.doctor()}
@app.post("/index")
def index(req: IndexRequest) -> dict[str, Any]:
return store.add_event(
content=req.content,
event_type=req.event_type,
source=req.source,
meta=req.meta,
)
@app.post("/search")
def search(req: SearchRequest) -> dict[str, Any]:
return {"query": req.query, "limit": req.limit, "results": store.search(req.query, req.limit)}
@app.get("/recent")
def recent(limit: int = 20) -> dict[str, Any]:
return {"limit": limit, "results": store.list_recent(limit)}
@app.get("/stats")
def stats() -> dict[str, Any]:
return store.stats()
@app.get("/summary")
def get_summary() -> dict[str, Any]:
return store.get_summary()
@app.post("/summary")
def set_summary(payload: dict[str, str]) -> dict[str, Any]:
summary = payload.get("summary", "").strip()
if not summary:
raise HTTPException(status_code=400, detail="summary is required")
return store.set_summary(summary)
@app.post("/purge")
def purge(req: PurgeRequest) -> dict[str, Any]:
return store.purge_older_than_days(req.days)
실행 방법
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
FastAPI는 이렇게 띄우면 됩니다.
uvicorn api:app --reload --host 0.0.0.0 --port 8001
MCP 서버는 stdio로 띄우는 게 가장 기본입니다.
python mcp_server.py
streamable-http로 쓰고 싶으면
MCP_TRANSPORT=streamable-http python mcp_server.py
공식 MCP 문서도 stdio와 streamable-http 같은 transport를 지원하는 예시를 제공합니다.
실제 사용 예시
이벤트 저장
{
"content": "사용자가 prod 배포 후 timeout 에러를 보고함",
"event_type": "error",
"source": "slack",
"meta": {
"channel": "#incident",
"severity": "high"
}
}
검색
{
"query": "timeout error",
"limit": 5
}
결과는 bm25()로 정렬되고, snippet()으로 일부 문맥이 잘려 나옵니다. FTS5 공식 문서는 MATCH 검색과 bm25(), snippet() 같은 보조 함수를 명시합니다.
이 구조가 잘 맞는 경우
이 패턴은 아래에 특히 잘 맞습니다.
- 코드 리뷰 기록
- Slack/이슈 요약
- 세션 이벤트 추적
- 장기 작업의 중간 상태 저장
- AI 코딩 에이전트의 compact/restore
- 운영 이력 검색
반대로, 초고속 쓰기 경쟁이 심한 대규모 멀티유저 백엔드에는 SQLite 하나로 끝내기보다 별도 DB가 더 낫습니다. 여기서는 “로컬 세션 메모리 + 검색”이라는 목적에 맞춰 설계했습니다.
다음 단계로 바로 붙일 것
이제 이 기본형에 아래를 붙이면 꽤 쓸만해집니다.
ctx_fetch_and_index(url)
→ URL 내용을 긁어 와서 저장ctx_batch_index(items[])
→ 여러 이벤트 한 번에 저장ctx_compact()
→ 최근 이벤트를 요약해서session_summary갱신ctx_insight()
→ 최근 에러/파일/태그 통계 리포트deny/allow정책
→ 나중에ctx_execute를 붙일 때 필수
추천 운영 원칙
- 원본 데이터는 SQLite에만 저장하고, LLM에는 요약·검색 결과만 넘깁니다.
- 실행 도구는 최소화합니다.
ctx_search,ctx_stats,ctx_recent,ctx_compact처럼 읽기/요약 위주가 좋습니다. - 위험한 작업은 분리합니다.
ctx_execute는 별도 허가가 있어야만 쓰도록 둡니다. - 로컬이면 stdio, 원격이면 Streamable HTTP로 갑니다.
Claude Code 최적 세팅
Claude Code는 기본적으로 read-only는 허용, 편집/실행/네트워크는 승인 필요 구조입니다. 권한은 /permissions로 관리할 수 있고, settings.json과 훅으로 세밀하게 제어할 수 있습니다.
추천 파일 구조
project/
├─ CLAUDE.md
├─ .claude/
│ ├─ settings.json
│ ├─ hooks/
│ └─ rules/
└─ .mcp.json
Claude Code는 프로젝트와 ~/.claude에서 CLAUDE.md, settings.json, hooks, rules, memory를 읽고, 프로젝트 MCP 서버는 .mcp.json에 둡니다.
CLAUDE.md 는 이렇게
여기는 길게 쓰지 말고, 딱 프로젝트 컨텍스트 + 작업 원칙만 넣는 게 좋습니다. Claude Code는 CLAUDE.md를 세션마다 읽습니다.
# Project Instructions
- 이 프로젝트는 ctx-mode MCP 서버를 사용한다.
- 검색 우선, 원문 전체 출력 금지.
- ctx_search / ctx_stats / ctx_recent 우선 사용.
- 위험한 shell 명령은 실행 전 반드시 확인한다.
- 세션 요약은 compact 후에만 갱신한다.
.claude/settings.json 권장값
Claude Code는 ~/.claude/settings.json과 프로젝트 .claude/settings.json을 지원합니다. 권한 예시도 공식 문서에 있습니다.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read(*)",
"Grep(*)",
"Bash(npm run lint)",
"Bash(npm run test *)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf *)",
"Read(./.env)",
"Read(./secrets/**)"
]
},
"env": {
"MCP_TRANSPORT": "stdio"
}
}
핵심은 이겁니다.
Read,Grep는 넓게 허용Bash는 lint/test만 제한적으로 허용- 네트워크 다운로드, 파괴적 명령, 비밀 파일 읽기는 차단
- MCP 서버는
stdio로 돌림
훅 설정은 Claude Code 쪽이 제일 강합니다
Claude Code 훅은 PreToolUse, PreCompact, SessionStart, PostToolUse 같은 이벤트에 걸 수 있고, PreToolUse는 tool 이름을 기준으로 allow/deny/ask/defer를 제어할 수 있습니다. PreCompact는 자동/수동 compact 직전에 요약이나 상태 갱신에 쓰기 좋습니다.
추천 훅 배치
SessionStart
→ 마지막 세션 요약 읽어서 복구PreToolUse
→ 위험 명령 차단,ctx_execute는 별도 검사PostToolUse
→ 이벤트 DB에 기록PreCompact
→ 최근 이벤트를 요약하고session_summary갱신
이 조합이 Claude Code에서는 가장 실용적입니다. 훅이 여러 이벤트에 걸쳐 지원되고, prompt/agent 훅까지 확장 가능합니다.
Cursor 최적 세팅
Cursor는 rules + MCP + permissions 조합이 핵심입니다. 프로젝트 규칙은 .cursor/rules에 두고, 규칙 우선순위는 Team → Project → User 순입니다.
추천 파일 구조
project/
├─ .cursor/
│ ├─ rules/
│ └─ mcp.json
└─ AGENTS.md
Cursor는 프로젝트 규칙을 .cursor/rules에 버전관리 파일로 저장하고, AGENTS.md는 더 단순한 대안입니다.
.cursor/rules 추천
여기는 코드 스타일보다 에이전트 행동 규칙을 넣는 게 중요합니다.
---
description: Context Mode usage rule
alwaysApply: true
---
- 원문 로그를 통째로 붙이지 말고 ctx_search를 먼저 사용한다.
- 요약이 가능하면 요약본만 반환한다.
- 파일 변경 전에는 변경 범위를 먼저 설명한다.
- 위험한 shell 명령은 반드시 확인한다.
Cursor 문서상 alwaysApply, description, globs로 적용 범위를 조절할 수 있고, 규칙은 특정 파일에만 붙이거나 자동 적용할 수 있습니다.
.cursor/mcp.json
Cursor는 프로젝트별 .cursor/mcp.json, 전역 ~/.cursor/mcp.json을 지원합니다. stdio 서버는 command, args, env, envFile을 쓰고, 원격 서버는 url, headers를 씁니다.
{
"mcpServers": {
"ctx-mode": {
"command": "python",
"args": ["./mcp_server.py"],
"env": {
"MCP_TRANSPORT": "stdio"
}
}
}
}
원격 서버
{
"mcpServers": {
"ctx-mode": {
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer ${env:CTX_MODE_TOKEN}"
}
}
}
}
Cursor는 MCP를 통해 Tools, Prompts, Resources를 지원하고, stdio, SSE, Streamable HTTP를 사용합니다.
permissions.json
Cursor는 MCP 도구를 기본적으로 사용자 승인 후 사용하고, 자동 실행하려면 ~/.cursor/permissions.json에 미리 넣을 수 있습니다.
ctx_search,ctx_stats,ctx_recent→ auto-run 허용ctx_execute,ctx_purge→ 승인 필요rm,curl,wget같은 터미널 명령은 자동 실행 금지
Claude Code와 Cursor를 같이 쓸 때 제일 좋은 조합
Claude Code
- 권한 제어: 강하게
- 훅: 적극적으로
- compact: 자동/수동 모두 활용
- 세션 복구:
PreCompact와SessionStart중심
Cursor
- 규칙: 프로젝트 단위로 명확하게
- MCP: 프로젝트
.cursor/mcp.json로 고정 - auto-run: 읽기 전용 도구만 제한 허용
- 팀 배포: Team Rules / Team marketplace 활용
Cursor 팀 규칙은 프로젝트/유저보다 우선하고, 강제(enforce)하면 사용자가 끌 수 없습니다.
당신의 ctx-mode 서버에 맞춘 권장값
이 서버는 검색형 메모리 + 세션 복구형 MCP이므로, 아래처럼 나누는 게 가장 좋습니다.
반드시 켤 것
ctx_searchctx_recentctx_statsctx_doctorctx_get_summaryctx_set_summary
조건부로만 쓸 것
ctx_indexctx_purgectx_executectx_batch_executectx_fetch_and_index
즉, 모델이 항상 쓸 수 있는 건 읽기/검색/요약이고, 쓰기/삭제/실행은 승인형으로 두는 게 안전합니다. Claude Code는 기본적으로 추가 동작에 승인이 필요하고, Cursor도 MCP 도구를 기본 승인형으로 다룹니다.
가장 실전적인 추천 세트
개인 개발자용
- MCP transport:
stdio - Claude Code:
allow는 최소,deny는 강하게 - Cursor:
.cursor/rules+.cursor/mcp.json - auto-run: 검색/조회만
팀 공유용
- MCP transport:
Streamable HTTP - 인증: bearer token
CLAUDE.md/AGENTS.md/.cursor/rules에 공통 원칙 명시- 관리형 정책으로 MCP 서버와 권한을 제한
MCP 공식 문서는 Streamable HTTP에서 인증과 Origin 검증이 중요하다고 말하고, Cursor는 원격 서버 등록과 OAuth/헤더 인증을 지원합니다.
바로 적용용 최소 세팅
Claude Code
.claude/settings.json작성CLAUDE.md작성mcp_server.py는stdio로 실행PreToolUse+PreCompact훅 추가Read/Grep위주 허용,Bash는 제한 허용
Cursor
.cursor/rules작성.cursor/mcp.json작성~/.cursor/permissions.json에 검색 도구만 auto-run 등록- 팀이면 Team Rules로 공통 정책 배포
- AGENTS.md는 필요할 때만 추가
댓글