codex-dev-mcp-suite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/CODEX_DEV_MCP_GUIDE.md +297 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/_testkit/harness.mjs +126 -0
- package/backfill-sessions-v2.mjs +288 -0
- package/bin/checkpoint.mjs +2 -0
- package/bin/context-pack.mjs +2 -0
- package/bin/devjournal.mjs +2 -0
- package/bin/project-memory.mjs +2 -0
- package/checkpoint/package.json +14 -0
- package/checkpoint/server.js +293 -0
- package/checkpoint/test.mjs +73 -0
- package/context-pack/package.json +14 -0
- package/context-pack/server.js +256 -0
- package/context-pack/test.mjs +66 -0
- package/devjournal/package.json +14 -0
- package/devjournal/rerank.js +89 -0
- package/devjournal/server.js +310 -0
- package/devjournal/test.mjs +68 -0
- package/package.json +65 -0
- package/project-memory/embedding.js +80 -0
- package/project-memory/package.json +14 -0
- package/project-memory/rerank.js +89 -0
- package/project-memory/server.js +448 -0
- package/project-memory/test.mjs +72 -0
- package/project-memory/test.rerank.mjs +53 -0
- package/run-tests.mjs +42 -0
package/.env.example
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# All network features are OPTIONAL. Without them, recall falls back to keyword
|
|
2
|
+
# search (fully offline). Any OpenAI-compatible endpoint works: OpenAI, Ollama,
|
|
3
|
+
# LM Studio, OpenRouter, vLLM, LiteLLM, 9router, etc.
|
|
4
|
+
|
|
5
|
+
# Base URL of an OpenAI-compatible API (must expose /v1/...)
|
|
6
|
+
LLM_BASE_URL=http://localhost:11434/v1
|
|
7
|
+
# API key for that endpoint (leave blank for local servers that don't need one)
|
|
8
|
+
LLM_API_KEY=
|
|
9
|
+
|
|
10
|
+
# Optional: semantic embedding recall (needs /v1/embeddings)
|
|
11
|
+
EMBED_MODEL=nomic-embed-text
|
|
12
|
+
EMBED_TIMEOUT_MS=12000
|
|
13
|
+
|
|
14
|
+
# Optional: LLM rerank recall (needs /v1/chat/completions)
|
|
15
|
+
# Used automatically when embeddings are unavailable.
|
|
16
|
+
RERANK_MODEL=llama3.1:8b
|
|
17
|
+
RERANK_TIMEOUT_MS=30000
|
|
18
|
+
RERANK_ENABLED=1
|
|
19
|
+
|
|
20
|
+
# Storage locations (defaults shown; override if you want)
|
|
21
|
+
# MEMORY_VAULT_DIR=~/.codex/memories/vault
|
|
22
|
+
# JOURNAL_DIR=~/.codex/memories/journal
|
|
23
|
+
# CHECKPOINT_DIR=~/.codex/memories/checkpoints
|
|
24
|
+
|
|
25
|
+
# Backward-compatible aliases (still honored): NINEROUTER_URL, NINEROUTER_KEY,
|
|
26
|
+
# EMBED_URL, EMBED_KEY, RERANK_URL, RERANK_KEY
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Codex Dev MCP Suite — Solo Dev / Vibecoder Toolkit
|
|
2
|
+
|
|
3
|
+
Built for sessions that hit "input too long" / get compacted, so you can stop
|
|
4
|
+
copy-pasting context across sessions. All servers are local, file-based, and
|
|
5
|
+
split storage per-project (by directory path hash), so projects never mix.
|
|
6
|
+
|
|
7
|
+
Servers live in `~/.codex/mcp-servers/<name>/` and are registered in
|
|
8
|
+
`~/.codex/config.toml`. Restart/reload Codex to load them.
|
|
9
|
+
|
|
10
|
+
Shared storage root: `~/.codex/memories/`
|
|
11
|
+
- `memories/vault/` → project-memory notes
|
|
12
|
+
- `memories/checkpoints/` → checkpoint snapshots
|
|
13
|
+
- `memories/journal/` → devjournal logs + handoffs
|
|
14
|
+
|
|
15
|
+
`dir` argument defaults to the current working directory on every tool, so in
|
|
16
|
+
normal use you don't pass it — context auto-separates per project folder.
|
|
17
|
+
|
|
18
|
+
--------------------------------------------------------------------------------
|
|
19
|
+
## 1) project-memory — searchable Markdown knowledge vault
|
|
20
|
+
Obsidian-style `.md` notes + Context7-style on-demand recall (keyword index).
|
|
21
|
+
|
|
22
|
+
Tools:
|
|
23
|
+
- memory_save {title, content, tags?, kind?, dir?} → save + auto-index a note
|
|
24
|
+
- memory_recall {query, limit?, full?, dir?} → pull most relevant notes
|
|
25
|
+
- memory_list {limit?, dir?} → list notes (newest first)
|
|
26
|
+
- memory_get {id, dir?} → full Markdown of one note
|
|
27
|
+
- memory_delete {id, dir?} → remove a note
|
|
28
|
+
|
|
29
|
+
Use it for: durable knowledge — decisions, gotchas, API quirks, snippets.
|
|
30
|
+
Recall is keyword-based (fast, local, no embeddings).
|
|
31
|
+
|
|
32
|
+
Example flow:
|
|
33
|
+
1. After solving something: memory_save title="Fix CORS on /api" content="..." tags=["cors","api"]
|
|
34
|
+
2. Next session: memory_recall query="how did we fix cors" → returns that note.
|
|
35
|
+
|
|
36
|
+
--------------------------------------------------------------------------------
|
|
37
|
+
## 2) devjournal — session timeline + handoff/resume (anti-compaction)
|
|
38
|
+
The key tool for your "session penuh / limit" problem.
|
|
39
|
+
|
|
40
|
+
Tools:
|
|
41
|
+
- journal_log {title, body?, type?, dir?} type: note|decision|blocker|done|idea
|
|
42
|
+
- journal_handoff {summary, next_steps?, open_questions?, active_files?, dir?}
|
|
43
|
+
- journal_resume {recent?, dir?} → latest handoff + recent entries
|
|
44
|
+
- journal_timeline {limit?, type?, dir?}
|
|
45
|
+
- journal_search {query, limit?, dir?} → relevance search (rerank/keyword)
|
|
46
|
+
- journal_clear_handoff {dir?}
|
|
47
|
+
|
|
48
|
+
Use it for: never losing your place.
|
|
49
|
+
- BEFORE a session ends / before context compacts: journal_handoff with what you
|
|
50
|
+
did + next steps + active files.
|
|
51
|
+
- START of a new session: journal_resume → instantly rebuild context, no paste.
|
|
52
|
+
|
|
53
|
+
Storage is append-only `journal.jsonl` + human-readable `journal.md` mirror +
|
|
54
|
+
canonical `handoff.json`.
|
|
55
|
+
|
|
56
|
+
--------------------------------------------------------------------------------
|
|
57
|
+
## 3) checkpoint — git-independent file snapshots (safe vibecoding)
|
|
58
|
+
Snapshot/restore project text files so you can experiment freely and roll back.
|
|
59
|
+
|
|
60
|
+
Tools:
|
|
61
|
+
- checkpoint_create {label?, dir?} → snapshot all text files now
|
|
62
|
+
- checkpoint_list {dir?}
|
|
63
|
+
- checkpoint_diff {id, dir?} → added / modified / deleted vs now
|
|
64
|
+
- checkpoint_restore {id, clean?, dir?} → revert files (clean=true also removes new files)
|
|
65
|
+
- checkpoint_delete {id, dir?}
|
|
66
|
+
|
|
67
|
+
Use it for: "let me try a wild refactor" without committing to git.
|
|
68
|
+
- checkpoint_create label="before experiment"
|
|
69
|
+
- ...make changes...
|
|
70
|
+
- checkpoint_diff <id> to see what moved, checkpoint_restore <id> to undo.
|
|
71
|
+
|
|
72
|
+
Notes/limits: skips binary files and files >2MB; ignores node_modules/.git/
|
|
73
|
+
build dirs; caps at 4000 files. Content-addressed storage dedupes unchanged files.
|
|
74
|
+
|
|
75
|
+
--------------------------------------------------------------------------------
|
|
76
|
+
## 4) context-pack — token-efficient project briefing
|
|
77
|
+
Get oriented in a project without dumping files into context.
|
|
78
|
+
|
|
79
|
+
Tools:
|
|
80
|
+
- pack_overview {include_readme?, dir?} → stack, scripts, key files, top dirs, README excerpt
|
|
81
|
+
- pack_tree {max_depth?, max_entries?, dir?}
|
|
82
|
+
- pack_outline {file, dir?} → top-level functions/classes/exports only
|
|
83
|
+
- pack_search {query, limit?, dir?} → fast path/filename match
|
|
84
|
+
|
|
85
|
+
Use it for: session start in an unfamiliar/large repo. pack_overview first,
|
|
86
|
+
then pack_outline on the few files that matter — far cheaper than reading whole files.
|
|
87
|
+
Detects: Node/JS (+ Next/React/Vue/Svelte/Express/TS/Tailwind/Prisma/Vite),
|
|
88
|
+
Python, Rust, Go, Java/JVM, PHP, Ruby, Docker.
|
|
89
|
+
|
|
90
|
+
--------------------------------------------------------------------------------
|
|
91
|
+
## Recommended daily loop
|
|
92
|
+
1. Session start: context-pack.pack_overview + devjournal.journal_resume + memory_recall (topic)
|
|
93
|
+
2. Before risky change: checkpoint.checkpoint_create
|
|
94
|
+
3. While working: memory_save (durable facts), journal_log (events/blockers)
|
|
95
|
+
4. Session end / context about to compact: journal_handoff
|
|
96
|
+
|
|
97
|
+
## Maintenance
|
|
98
|
+
- All data is plain files under ~/.codex/memories/ — back up or inspect freely.
|
|
99
|
+
- To wipe one project's data, delete its slug folder under each store.
|
|
100
|
+
- Env overrides: MEMORY_VAULT_DIR, CHECKPOINT_DIR, JOURNAL_DIR.
|
|
101
|
+
|
|
102
|
+
================================================================================
|
|
103
|
+
## UPGRADE 1 — Semantic (embedding) recall in project-memory
|
|
104
|
+
project-memory now stores a semantic embedding per note and uses it for recall
|
|
105
|
+
when available. Hybrid scoring: 0.8 * cosine(semantic) + 0.2 * keyword.
|
|
106
|
+
|
|
107
|
+
- Embeddings via 9router (`/v1/embeddings`), model `bm/baai/bge-m3`.
|
|
108
|
+
- Configured in config.toml under [mcp_servers.project-memory.env]:
|
|
109
|
+
NINEROUTER_URL, NINEROUTER_KEY, EMBED_MODEL, EMBED_TIMEOUT_MS.
|
|
110
|
+
- GRACEFUL FALLBACK: if the embedding endpoint is unavailable (e.g. 503 "no
|
|
111
|
+
channel"), recall automatically falls back to keyword search. Nothing breaks.
|
|
112
|
+
- memory_recall output is tagged with the mode used: [semantic] or [keyword],
|
|
113
|
+
and per-note shows sim:0.xxx (semantic) or score:N (keyword).
|
|
114
|
+
|
|
115
|
+
New tool:
|
|
116
|
+
- memory_reindex {force?, dir?} → backfill embeddings for notes that don't have
|
|
117
|
+
one yet. Run it once the bge-m3 channel is live on your 9router so old notes
|
|
118
|
+
become semantically searchable. Safe to run repeatedly; force=true re-embeds all.
|
|
119
|
+
|
|
120
|
+
Status note: at build time the bge-m3 channel returned 503, so notes are saved
|
|
121
|
+
keyword-only and recall runs in [keyword] mode. The moment the channel is up,
|
|
122
|
+
new saves embed automatically and `memory_reindex` upgrades old ones — no code
|
|
123
|
+
changes needed.
|
|
124
|
+
|
|
125
|
+
To override the embedding model/endpoint: set EMBED_MODEL / NINEROUTER_URL /
|
|
126
|
+
NINEROUTER_KEY (or EMBED_URL / EMBED_KEY) in the env block.
|
|
127
|
+
|
|
128
|
+
================================================================================
|
|
129
|
+
## UPGRADE 2 — auto-capture plugin (hooks)
|
|
130
|
+
A Codex plugin that removes the manual step of pasting context across sessions.
|
|
131
|
+
|
|
132
|
+
Marketplace: `codex-dev-suite` (local) at
|
|
133
|
+
~/.codex/marketplaces/codex-dev-suite
|
|
134
|
+
Plugin: `auto-capture` (installed + enabled).
|
|
135
|
+
|
|
136
|
+
What it does:
|
|
137
|
+
- SessionStart (matcher: startup|clear|compact|resume): runs
|
|
138
|
+
scripts/session-start.sh, which finds the devjournal handoff + recent entries
|
|
139
|
+
for the CURRENT project (same slug algorithm as the MCP servers) and prints
|
|
140
|
+
them to stdout so Codex injects them as context. This is the anti-compaction
|
|
141
|
+
piece: after a compact/clear/resume, your handoff reappears automatically.
|
|
142
|
+
- PostToolUse (async, throttled): runs scripts/post-tool.sh, which counts tool
|
|
143
|
+
calls per project and, after >=25 calls and at most once per 30 min, emits a
|
|
144
|
+
short reminder to call journal_handoff / memory_save so work survives compaction.
|
|
145
|
+
|
|
146
|
+
Trust: Codex requires you to TRUST the hook the first time. On your next session
|
|
147
|
+
start in a trusted project, Codex will prompt to trust the auto-capture hook —
|
|
148
|
+
approve it once. (Advanced/automation only: `--dangerously-bypass-hook-trust`.)
|
|
149
|
+
|
|
150
|
+
Tuning (env, optional):
|
|
151
|
+
- AUTOCAP_THROTTLE seconds between nudges (default 1800)
|
|
152
|
+
- AUTOCAP_STATE_DIR where call counters live (default ~/.codex/memories/.autocap)
|
|
153
|
+
|
|
154
|
+
Manage:
|
|
155
|
+
- codex plugin list | grep auto-capture
|
|
156
|
+
- codex plugin remove auto-capture@codex-dev-suite
|
|
157
|
+
- Edit scripts under ~/.codex/marketplaces/codex-dev-suite/plugins/auto-capture/
|
|
158
|
+
then re-run `codex plugin marketplace add ~/.codex/marketplaces/codex-dev-suite`
|
|
159
|
+
and `codex plugin add auto-capture@codex-dev-suite` to refresh the cached copy.
|
|
160
|
+
|
|
161
|
+
How the pieces fit:
|
|
162
|
+
auto-capture (SessionStart) --reads--> devjournal handoff --so--> you resume instantly
|
|
163
|
+
auto-capture (PostToolUse) --reminds--> you to journal_handoff / memory_save
|
|
164
|
+
project-memory.memory_recall --semantic/keyword--> durable facts on demand
|
|
165
|
+
|
|
166
|
+
================================================================================
|
|
167
|
+
## Testing
|
|
168
|
+
A dependency-free test suite covers all four servers (26 tests).
|
|
169
|
+
|
|
170
|
+
Run everything:
|
|
171
|
+
cd ~/.codex/mcp-servers && node run-tests.mjs
|
|
172
|
+
|
|
173
|
+
Run one server:
|
|
174
|
+
cd ~/.codex/mcp-servers/<server> && npm test
|
|
175
|
+
|
|
176
|
+
Layout:
|
|
177
|
+
- _testkit/harness.mjs shared MCP stdio client + tiny assert/test runner
|
|
178
|
+
- <server>/test.mjs per-server tests (spawn server, call tools, assert)
|
|
179
|
+
- run-tests.mjs runs all suites, prints combined summary
|
|
180
|
+
|
|
181
|
+
Tests run fully offline (project-memory forces keyword mode by passing an empty
|
|
182
|
+
embedding key), use temp dirs, and clean up after themselves — safe to run anytime.
|
|
183
|
+
|
|
184
|
+
================================================================================
|
|
185
|
+
## Backfill — import past Codex sessions
|
|
186
|
+
Imports your existing Codex session history (~/.codex/sessions/*.jsonl) into
|
|
187
|
+
project-memory + devjournal, grouped by each session's cwd (project).
|
|
188
|
+
|
|
189
|
+
USE backfill-sessions-v2.mjs (see "Backfill v2" below). The original v1 script
|
|
190
|
+
and its state file (.backfill-state.json) have been removed in favor of v2,
|
|
191
|
+
which extracts deeper content (commands, plan, files, tool counts) and backdates
|
|
192
|
+
entries to the original session time.
|
|
193
|
+
|
|
194
|
+
Embeddings: backfill runs with embeddings OFF (keyword-only) for speed. Once the
|
|
195
|
+
bge-m3 channel on 9router is live, run memory_reindex per project to upgrade old
|
|
196
|
+
notes to semantic search. (LLM rerank still works in the meantime — see UPGRADE 3/4.)
|
|
197
|
+
|
|
198
|
+
================================================================================
|
|
199
|
+
## UPGRADE 3 — LLM rerank (Kiro) when embeddings are unavailable
|
|
200
|
+
Since the bge-m3 embedding channel is currently 503, project-memory now has a
|
|
201
|
+
middle tier between semantic and keyword: an LLM reranker using a 9router chat
|
|
202
|
+
model (default Kiro `kr/claude-haiku-4.5`).
|
|
203
|
+
|
|
204
|
+
Recall mode precedence (auto-selected, shown in output as [mode]):
|
|
205
|
+
1. [semantic] — if note embeddings + query embedding are available (bge-m3)
|
|
206
|
+
2. [rerank] — keyword prefilter (top ~20; falls back to most-recent if no
|
|
207
|
+
keyword hit) → chat model picks/orders the most relevant → ids
|
|
208
|
+
3. [keyword] — pure keyword scoring (always-available fallback)
|
|
209
|
+
|
|
210
|
+
Why this matters: rerank handles semantic queries with zero keyword overlap.
|
|
211
|
+
Example: query "how do users sign in securely with tokens" correctly returns a
|
|
212
|
+
note titled "JWT auth flow" — no shared words.
|
|
213
|
+
|
|
214
|
+
Config (config.toml, [mcp_servers.project-memory.env]):
|
|
215
|
+
RERANK_MODEL = "kr/claude-haiku-4.5" # any 9router chat model id
|
|
216
|
+
RERANK_TIMEOUT_MS = "30000"
|
|
217
|
+
RERANK_ENABLED = "1" # set "0" to force keyword-only
|
|
218
|
+
|
|
219
|
+
Files: rerank.js (chat client + parser), wired into server.js recall().
|
|
220
|
+
Graceful: any failure/timeout/non-200 falls back to keyword. Never throws.
|
|
221
|
+
|
|
222
|
+
Cost note: rerank makes ONE small chat call per recall (haiku, temp 0,
|
|
223
|
+
~200 max tokens). Cheap, but it does use your 9router quota. Disable with
|
|
224
|
+
RERANK_ENABLED=0 if you want zero-cost keyword-only recall.
|
|
225
|
+
|
|
226
|
+
Tests: project-memory/test.rerank.mjs (online; auto-skips if 9router is down).
|
|
227
|
+
|
|
228
|
+
================================================================================
|
|
229
|
+
## UPGRADE 4 — devjournal journal_search (rerank)
|
|
230
|
+
devjournal now has a relevance search tool, mirroring project-memory's rerank:
|
|
231
|
+
|
|
232
|
+
- journal_search {query, limit?, dir?}
|
|
233
|
+
Keyword prefilter over journal entries, then LLM rerank (9router Kiro
|
|
234
|
+
kr/claude-haiku-4.5) when available; else keyword scoring. Output tagged
|
|
235
|
+
[rerank] or [keyword]. Falls back to recent entries for semantic-style
|
|
236
|
+
queries with no keyword overlap.
|
|
237
|
+
|
|
238
|
+
journal_resume is intentionally unchanged — it returns the canonical latest
|
|
239
|
+
handoff + recent entries (no query). Use journal_search when you want to find
|
|
240
|
+
entries about a specific topic across past sessions.
|
|
241
|
+
|
|
242
|
+
Files: devjournal/rerank.js (same module as project-memory), wired into
|
|
243
|
+
devjournal/server.js. Same env knobs apply (RERANK_MODEL / RERANK_ENABLED /
|
|
244
|
+
RERANK_TIMEOUT_MS); they're already set in config.toml for project-memory, and
|
|
245
|
+
devjournal reads NINEROUTER_URL/KEY from its own env block — note: enable rerank
|
|
246
|
+
for devjournal by adding the RERANK_* + NINEROUTER_* vars to
|
|
247
|
+
[mcp_servers.devjournal.env] too (see below).
|
|
248
|
+
|
|
249
|
+
================================================================================
|
|
250
|
+
## Backfill v2 — deep extraction + original timestamps
|
|
251
|
+
backfill-sessions-v2.mjs supersedes v1. Per session it now extracts:
|
|
252
|
+
- all user prompts, the plan steps (update_plan), shell commands run, files
|
|
253
|
+
touched (apply_patch paths + cat/rm/cp/redirect heuristics), tool usage
|
|
254
|
+
counts, turn/command/reasoning counts, and the final assistant note.
|
|
255
|
+
- The note is BACKDATED to the session's original start time.
|
|
256
|
+
|
|
257
|
+
New tool args enabling this:
|
|
258
|
+
- memory_save ... created: "<ISO>" # backdate the note
|
|
259
|
+
- journal_log ... ts: "<ISO>" # backdate the entry
|
|
260
|
+
|
|
261
|
+
Usage:
|
|
262
|
+
cd ~/.codex/mcp-servers
|
|
263
|
+
node backfill-sessions-v2.mjs --dry --min-prompts 2 # preview deep extract
|
|
264
|
+
node backfill-sessions-v2.mjs --min-prompts 2 --wipe # replace old session
|
|
265
|
+
# entries, re-import deep
|
|
266
|
+
node backfill-sessions-v2.mjs --min-prompts 2 # incremental (new only)
|
|
267
|
+
|
|
268
|
+
--wipe removes existing kind/type=="session" entries (from any prior backfill)
|
|
269
|
+
before re-importing, so you don't get duplicates. Source of truth is always
|
|
270
|
+
~/.codex/sessions, so this is reversible.
|
|
271
|
+
|
|
272
|
+
State: ~/.codex/memories/.backfill-v2-state.json (separate from v1).
|
|
273
|
+
Result: 87 sessions re-imported with original timestamps (2026-03-16 .. 2026-06-13),
|
|
274
|
+
0 errors; timestamps verified 87/87 matching original session dates.
|
|
275
|
+
|
|
276
|
+
================================================================================
|
|
277
|
+
## UPGRADE 5 — project-memory resources (browsable notes)
|
|
278
|
+
project-memory now also exposes every saved note as a read-only MCP resource,
|
|
279
|
+
so notes can be browsed/opened without calling a tool.
|
|
280
|
+
|
|
281
|
+
- Capability: declares `resources` alongside `tools`.
|
|
282
|
+
- resources/list → one entry per note across ALL projects in the vault, with
|
|
283
|
+
name (title), description ([kind] tags — created), mimeType text/markdown.
|
|
284
|
+
- resources/read → full Markdown of a note.
|
|
285
|
+
- URI scheme: memory://<project-slug>/<noteId>
|
|
286
|
+
|
|
287
|
+
Note on naming: the MCP server id is `project-memory` (hyphen), as written in
|
|
288
|
+
config.toml. Some UIs show a tool namespace like `project_memory` (underscore) —
|
|
289
|
+
that label is NOT the server id. Resource/admin calls must target the real id
|
|
290
|
+
`project-memory`.
|
|
291
|
+
|
|
292
|
+
checkpoint / context-pack / devjournal remain tools-only by design (they're
|
|
293
|
+
actions, not browsable data). devjournal entries are best queried via
|
|
294
|
+
journal_search / journal_timeline.
|
|
295
|
+
|
|
296
|
+
Verified: resources/list returned all current notes; resources/read returned a
|
|
297
|
+
note's Markdown. Full test suite still 27/27.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 maximalabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Codex Dev MCP Suite
|
|
2
|
+
|
|
3
|
+
[](https://github.com/verrysimatupang99/codex-dev-mcp-suite/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Four local, file-based MCP servers for solo developers and vibecoders who keep
|
|
6
|
+
losing context when sessions hit "input too long" / get compacted / restart.
|
|
7
|
+
Stop re-pasting context across sessions.
|
|
8
|
+
|
|
9
|
+
All servers are **local, dependency-light (only the MCP SDK), and split storage
|
|
10
|
+
per-project** by the working directory. Works with any MCP-capable client
|
|
11
|
+
(Codex CLI, Claude Code, Cursor, Cline, etc.).
|
|
12
|
+
|
|
13
|
+
## The four servers
|
|
14
|
+
|
|
15
|
+
| Server | What it does | Key tools |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| **project-memory** | Searchable Markdown knowledge vault (Obsidian-style notes + on-demand recall). Notes are also exposed as MCP resources. | `memory_save`, `memory_recall`, `memory_list`, `memory_get`, `memory_delete`, `memory_reindex` |
|
|
18
|
+
| **devjournal** | Per-project session timeline + handoff/resume (anti-compaction). | `journal_log`, `journal_handoff`, `journal_resume`, `journal_timeline`, `journal_search`, `journal_clear_handoff` |
|
|
19
|
+
| **checkpoint** | Git-independent file snapshots for safe experimentation. | `checkpoint_create`, `checkpoint_list`, `checkpoint_diff`, `checkpoint_restore`, `checkpoint_delete` |
|
|
20
|
+
| **context-pack** | Token-efficient project briefing (stack, tree, symbols, search). | `pack_overview`, `pack_tree`, `pack_outline`, `pack_search` |
|
|
21
|
+
|
|
22
|
+
## Recall quality (project-memory & devjournal)
|
|
23
|
+
|
|
24
|
+
Recall auto-selects the best available mode:
|
|
25
|
+
|
|
26
|
+
1. **semantic** — if an embeddings endpoint is configured (any OpenAI-compatible `/v1/embeddings`: OpenAI, Ollama, LM Studio, OpenRouter, vLLM, LiteLLM, 9router, ...)
|
|
27
|
+
2. **rerank** — keyword prefilter then an LLM reranker (any OpenAI-compatible chat model)
|
|
28
|
+
3. **keyword** — always-available offline fallback
|
|
29
|
+
|
|
30
|
+
All network features degrade gracefully: no endpoint = keyword mode, never an error.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Requires Node.js >= 18.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/<you>/codex-dev-mcp-suite.git
|
|
38
|
+
cd codex-dev-mcp-suite
|
|
39
|
+
# install the MCP SDK in each server
|
|
40
|
+
for s in project-memory checkpoint context-pack devjournal; do (cd "$s" && npm install); done
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Register with your MCP client
|
|
44
|
+
|
|
45
|
+
**Codex CLI** (`~/.codex/config.toml`):
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[mcp_servers.project-memory]
|
|
49
|
+
command = "node"
|
|
50
|
+
args = ["/abs/path/codex-dev-mcp-suite/project-memory/server.js"]
|
|
51
|
+
[mcp_servers.project-memory.env]
|
|
52
|
+
MEMORY_VAULT_DIR = "~/.codex/memories/vault"
|
|
53
|
+
# optional recall upgrades (see .env.example)
|
|
54
|
+
# LLM_BASE_URL = "http://localhost:11434/v1" # any OpenAI-compatible endpoint
|
|
55
|
+
# LLM_API_KEY = "..."
|
|
56
|
+
# RERANK_MODEL = "llama3.1:8b"
|
|
57
|
+
|
|
58
|
+
[mcp_servers.checkpoint]
|
|
59
|
+
command = "node"
|
|
60
|
+
args = ["/abs/path/codex-dev-mcp-suite/checkpoint/server.js"]
|
|
61
|
+
|
|
62
|
+
[mcp_servers.context-pack]
|
|
63
|
+
command = "node"
|
|
64
|
+
args = ["/abs/path/codex-dev-mcp-suite/context-pack/server.js"]
|
|
65
|
+
|
|
66
|
+
[mcp_servers.devjournal]
|
|
67
|
+
command = "node"
|
|
68
|
+
args = ["/abs/path/codex-dev-mcp-suite/devjournal/server.js"]
|
|
69
|
+
[mcp_servers.devjournal.env]
|
|
70
|
+
JOURNAL_DIR = "~/.codex/memories/journal"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Claude Code / Cursor / Cline** (`mcpServers` JSON): same idea — `command: "node"`,
|
|
74
|
+
`args: ["/abs/path/<server>/server.js"]`, optional `env`.
|
|
75
|
+
|
|
76
|
+
## Daily workflow
|
|
77
|
+
|
|
78
|
+
- Session start: `pack_overview` + `journal_resume` + `memory_recall "<topic>"`
|
|
79
|
+
- Before a risky change: `checkpoint_create`
|
|
80
|
+
- While working: `memory_save` (durable facts), `journal_log` (events/blockers)
|
|
81
|
+
- Session end / before compaction: `journal_handoff`
|
|
82
|
+
|
|
83
|
+
## Optional: import your existing Codex history
|
|
84
|
+
|
|
85
|
+
`backfill-sessions-v2.mjs` reads `~/.codex/sessions/*.jsonl` and imports each
|
|
86
|
+
session (prompts, plan, commands, files touched) into project-memory + devjournal,
|
|
87
|
+
grouped by project, backdated to the original session time.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node backfill-sessions-v2.mjs --dry --min-prompts 2 # preview
|
|
91
|
+
node backfill-sessions-v2.mjs --min-prompts 2 # import
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Tests
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
node run-tests.mjs # all servers (offline, ~27 tests)
|
|
98
|
+
cd project-memory && npm test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Privacy
|
|
102
|
+
|
|
103
|
+
Everything is stored as plain files on your machine. No data leaves your
|
|
104
|
+
computer unless you explicitly configure an embeddings/rerank endpoint. Your
|
|
105
|
+
personal vault/journal/checkpoints are gitignored.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal MCP stdio test harness — zero external deps.
|
|
3
|
+
* Provides: McpClient (spawn server, initialize, callTool), and a tiny
|
|
4
|
+
* assert/test runner (describe/it/run) with colored-ish plain output.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
|
|
8
|
+
export class McpClient {
|
|
9
|
+
constructor(serverPath, env = {}) {
|
|
10
|
+
this.serverPath = serverPath;
|
|
11
|
+
this.env = { ...process.env, ...env };
|
|
12
|
+
this.proc = null;
|
|
13
|
+
this.buf = "";
|
|
14
|
+
this.pending = new Map();
|
|
15
|
+
this.nextId = 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async start() {
|
|
19
|
+
this.proc = spawn("node", [this.serverPath], { env: this.env, stdio: ["pipe", "pipe", "ignore"] });
|
|
20
|
+
this.proc.stdout.on("data", (d) => this._onData(d.toString()));
|
|
21
|
+
await this._rpc("initialize", {
|
|
22
|
+
protocolVersion: "2024-11-05",
|
|
23
|
+
capabilities: {},
|
|
24
|
+
clientInfo: { name: "testkit", version: "1.0.0" },
|
|
25
|
+
});
|
|
26
|
+
this._notify("notifications/initialized");
|
|
27
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_onData(chunk) {
|
|
31
|
+
this.buf += chunk;
|
|
32
|
+
let idx;
|
|
33
|
+
while ((idx = this.buf.indexOf("\n")) !== -1) {
|
|
34
|
+
const line = this.buf.slice(0, idx).trim();
|
|
35
|
+
this.buf = this.buf.slice(idx + 1);
|
|
36
|
+
if (!line) continue;
|
|
37
|
+
let msg;
|
|
38
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
39
|
+
if (msg.id != null && this.pending.has(msg.id)) {
|
|
40
|
+
const { resolve } = this.pending.get(msg.id);
|
|
41
|
+
this.pending.delete(msg.id);
|
|
42
|
+
resolve(msg);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_send(obj) { this.proc.stdin.write(JSON.stringify(obj) + "\n"); }
|
|
48
|
+
_notify(method, params) { this._send({ jsonrpc: "2.0", method, params }); }
|
|
49
|
+
|
|
50
|
+
_rpc(method, params, timeoutMs = 30000) {
|
|
51
|
+
const id = this.nextId++;
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
this.pending.delete(id);
|
|
55
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
56
|
+
}, timeoutMs);
|
|
57
|
+
this.pending.set(id, { resolve: (m) => { clearTimeout(timer); resolve(m); } });
|
|
58
|
+
this._send({ jsonrpc: "2.0", id, method, params });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async listTools() {
|
|
63
|
+
const res = await this._rpc("tools/list", {});
|
|
64
|
+
return (res.result?.tools || []).map((t) => t.name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async callTool(name, args = {}, timeoutMs = 30000) {
|
|
68
|
+
const res = await this._rpc("tools/call", { name, arguments: args }, timeoutMs);
|
|
69
|
+
if (res.error) throw new Error(`RPC error: ${res.error.message}`);
|
|
70
|
+
const text = res.result?.content?.[0]?.text ?? "";
|
|
71
|
+
return { text, isError: Boolean(res.result?.isError), raw: res.result };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async stop() {
|
|
75
|
+
try { this.proc?.kill(); } catch { /* ignore */ }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---- tiny test runner ----
|
|
80
|
+
const tests = [];
|
|
81
|
+
let currentSuite = "";
|
|
82
|
+
export function describe(name, fn) { currentSuite = name; fn(); currentSuite = ""; }
|
|
83
|
+
export function it(name, fn) { tests.push({ suite: currentSuite, name, fn }); }
|
|
84
|
+
|
|
85
|
+
export function assert(cond, msg) {
|
|
86
|
+
if (!cond) throw new Error("assertion failed: " + (msg || ""));
|
|
87
|
+
}
|
|
88
|
+
export function assertIncludes(haystack, needle, msg) {
|
|
89
|
+
if (!String(haystack).includes(needle)) {
|
|
90
|
+
throw new Error(`expected to include "${needle}"${msg ? " — " + msg : ""}\n got: ${String(haystack).slice(0, 300)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function assertEqual(a, b, msg) {
|
|
94
|
+
if (a !== b) throw new Error(`expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}${msg ? " — " + msg : ""}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function run() {
|
|
98
|
+
let pass = 0, fail = 0;
|
|
99
|
+
const failures = [];
|
|
100
|
+
for (const t of tests) {
|
|
101
|
+
const label = t.suite ? `${t.suite} › ${t.name}` : t.name;
|
|
102
|
+
try {
|
|
103
|
+
await t.fn();
|
|
104
|
+
pass++;
|
|
105
|
+
console.log(` ✓ ${label}`);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
fail++;
|
|
108
|
+
failures.push({ label, err: e });
|
|
109
|
+
console.log(` ✗ ${label}`);
|
|
110
|
+
console.log(` ${e.message.split("\n").join("\n ")}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log(`\n${pass} passed, ${fail} failed (${tests.length} total)`);
|
|
114
|
+
if (fail > 0) process.exitCode = 1;
|
|
115
|
+
return { pass, fail, failures };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// unique temp dir helper
|
|
119
|
+
import os from "os";
|
|
120
|
+
import path from "path";
|
|
121
|
+
import fs from "fs";
|
|
122
|
+
export function tmpDir(prefix = "mcp-test-") {
|
|
123
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
124
|
+
return d;
|
|
125
|
+
}
|
|
126
|
+
export function rmrf(p) { try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* ignore */ } }
|