fathom-mcp 0.1.5 → 0.2.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/CHANGELOG.md +12 -0
- package/README.md +35 -14
- package/package.json +6 -1
- package/src/cli.js +283 -95
- package/src/config.js +8 -3
- package/src/server-client.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 (2026-02-26)
|
|
4
|
+
|
|
5
|
+
Multi-agent support.
|
|
6
|
+
|
|
7
|
+
- **Multi-agent init wizard** — auto-detects installed agents and generates per-agent MCP configs
|
|
8
|
+
- **Supported agents:** Claude Code, OpenAI Codex, Gemini CLI, Cursor, VS Code Copilot, Windsurf
|
|
9
|
+
- **Per-agent config writers** — `.mcp.json`, `.codex/config.toml`, `.gemini/settings.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `~/.codeium/windsurf/mcp_config.json`
|
|
10
|
+
- **Conditional hooks** — hook setup only for Claude Code (other agents don't support hooks)
|
|
11
|
+
- **`agents` array** replaces legacy `architecture` string in `.fathom.json` — backward compatible
|
|
12
|
+
- **Server-side agent dispatch** — persistent sessions launch the correct agent CLI per workspace
|
|
13
|
+
- **Status command** — now shows configured agents per workspace
|
|
14
|
+
|
|
3
15
|
## 0.1.0 (2026-02-25)
|
|
4
16
|
|
|
5
17
|
Initial release.
|
package/README.md
CHANGED
|
@@ -9,9 +9,20 @@
|
|
|
9
9
|
hifathom.com · fathom@myrakrusemark.com
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
MCP server for [Fathom](https://hifathom.com) — vault operations, search, rooms, and cross-workspace communication.
|
|
12
|
+
MCP server for [Fathom](https://hifathom.com) — vault operations, search, rooms, and cross-workspace communication. Works with any MCP-compatible agent.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Supported Agents
|
|
15
|
+
|
|
16
|
+
| Agent | Config file | Auto-detected by |
|
|
17
|
+
|-------|------------|------------------|
|
|
18
|
+
| **Claude Code** | `.mcp.json` | `.claude/` directory |
|
|
19
|
+
| **OpenAI Codex** | `.codex/config.toml` | `.codex/` directory |
|
|
20
|
+
| **Gemini CLI** | `.gemini/settings.json` | `.gemini/` directory |
|
|
21
|
+
| **Cursor** | `.cursor/mcp.json` | `.cursor/` directory |
|
|
22
|
+
| **VS Code Copilot** | `.vscode/mcp.json` | `.vscode/` directory |
|
|
23
|
+
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | `~/.codeium/windsurf/` directory |
|
|
24
|
+
|
|
25
|
+
The init wizard auto-detects which agents you have and generates the right config for each.
|
|
15
26
|
|
|
16
27
|
## Quick Start
|
|
17
28
|
|
|
@@ -19,13 +30,14 @@ The MCP tools that let Claude Code interact with your vault. Reads/writes happen
|
|
|
19
30
|
npx fathom-mcp init
|
|
20
31
|
```
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
The wizard will:
|
|
34
|
+
1. Detect installed agents (Claude Code, Codex, Gemini, etc.)
|
|
35
|
+
2. Let you pick which ones to configure
|
|
36
|
+
3. Write per-agent MCP config files
|
|
37
|
+
4. Set up hooks (Claude Code only)
|
|
38
|
+
5. Register the workspace with your fathom-server
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
- `.fathom.json` — workspace config (server URL, API key, vault path)
|
|
26
|
-
- `.mcp.json` — registers `npx fathom-mcp` as an MCP server
|
|
27
|
-
- `.claude/settings.local.json` — hooks for context injection and precompact snapshots
|
|
28
|
-
- `vault/` — creates the directory if it doesn't exist
|
|
40
|
+
Restart your agent and fathom tools will be available.
|
|
29
41
|
|
|
30
42
|
## Prerequisites
|
|
31
43
|
|
|
@@ -35,7 +47,7 @@ The init wizard creates:
|
|
|
35
47
|
## Commands
|
|
36
48
|
|
|
37
49
|
```bash
|
|
38
|
-
npx fathom-mcp # Start MCP server (stdio — used by
|
|
50
|
+
npx fathom-mcp # Start MCP server (stdio — used by agent configs)
|
|
39
51
|
npx fathom-mcp init # Interactive setup wizard
|
|
40
52
|
npx fathom-mcp status # Check server connection + workspace status
|
|
41
53
|
```
|
|
@@ -64,7 +76,7 @@ npx fathom-mcp status # Check server connection + workspace status
|
|
|
64
76
|
| `fathom_room_list` | List all rooms |
|
|
65
77
|
| `fathom_room_describe` | Set a room's description/topic |
|
|
66
78
|
| `fathom_workspaces` | List all configured workspaces |
|
|
67
|
-
| `fathom_send` | Send a message to another workspace's
|
|
79
|
+
| `fathom_send` | Send a message to another workspace's agent instance |
|
|
68
80
|
|
|
69
81
|
## Configuration
|
|
70
82
|
|
|
@@ -76,8 +88,9 @@ npx fathom-mcp status # Check server connection + workspace status
|
|
|
76
88
|
"vault": "vault",
|
|
77
89
|
"server": "http://localhost:4243",
|
|
78
90
|
"apiKey": "fv_abc123...",
|
|
91
|
+
"agents": ["claude-code", "gemini"],
|
|
79
92
|
"hooks": {
|
|
80
|
-
"
|
|
93
|
+
"vault-recall": { "enabled": true },
|
|
81
94
|
"precompact-snapshot": { "enabled": true }
|
|
82
95
|
}
|
|
83
96
|
}
|
|
@@ -89,11 +102,19 @@ npx fathom-mcp status # Check server connection + workspace status
|
|
|
89
102
|
2. `.fathom.json` (walked up from cwd to filesystem root)
|
|
90
103
|
3. Built-in defaults
|
|
91
104
|
|
|
92
|
-
|
|
105
|
+
### Backward compatibility
|
|
106
|
+
|
|
107
|
+
The `agents` array replaces the legacy `architecture` string field. Old configs with `architecture: "claude-code"` are automatically migrated to `agents: ["claude-code"]` at read time.
|
|
108
|
+
|
|
109
|
+
## Hooks (Claude Code only)
|
|
110
|
+
|
|
111
|
+
Hooks are only available in Claude Code and are configured in `.claude/settings.local.json`.
|
|
112
|
+
|
|
113
|
+
**UserPromptSubmit** (`fathom-recall.sh`): Runs vault recall on every message — injects relevant context.
|
|
93
114
|
|
|
94
|
-
**
|
|
115
|
+
**PreCompact** (`fathom-precompact.sh`): Records which vault files were active before context compaction.
|
|
95
116
|
|
|
96
|
-
|
|
117
|
+
Other agents don't support hooks — they get the same MCP tools but without automatic context injection.
|
|
97
118
|
|
|
98
119
|
## Vault Frontmatter Schema
|
|
99
120
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fathom-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"vault",
|
|
38
38
|
"fathom",
|
|
39
39
|
"claude",
|
|
40
|
+
"codex",
|
|
41
|
+
"gemini",
|
|
42
|
+
"cursor",
|
|
43
|
+
"copilot",
|
|
44
|
+
"windsurf",
|
|
40
45
|
"ai-agent",
|
|
41
46
|
"memory"
|
|
42
47
|
]
|
package/src/cli.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from "fs";
|
|
13
|
+
import os from "os";
|
|
13
14
|
import path from "path";
|
|
14
15
|
import readline from "readline";
|
|
15
16
|
import { fileURLToPath } from "url";
|
|
@@ -105,6 +106,143 @@ function copyScripts(targetDir) {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// --- Agent registry ----------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
const MCP_SERVER_ENTRY = {
|
|
112
|
+
command: "npx",
|
|
113
|
+
args: ["-y", "fathom-mcp"],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Per-agent config writers. Each writes the appropriate MCP config file
|
|
118
|
+
* for that agent, merging with existing config if present.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
function writeMcpJson(cwd) {
|
|
122
|
+
const filePath = path.join(cwd, ".mcp.json");
|
|
123
|
+
const existing = readJsonFile(filePath) || {};
|
|
124
|
+
deepMerge(existing, { mcpServers: { "fathom-vault": MCP_SERVER_ENTRY } });
|
|
125
|
+
writeJsonFile(filePath, existing);
|
|
126
|
+
return ".mcp.json";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function writeCodexToml(cwd) {
|
|
130
|
+
const dir = path.join(cwd, ".codex");
|
|
131
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
132
|
+
const filePath = path.join(dir, "config.toml");
|
|
133
|
+
|
|
134
|
+
let content = "";
|
|
135
|
+
try {
|
|
136
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
137
|
+
} catch { /* file doesn't exist */ }
|
|
138
|
+
|
|
139
|
+
// Check if fathom-vault section already exists
|
|
140
|
+
if (/\[mcp_servers\.fathom-vault\]/.test(content)) {
|
|
141
|
+
return ".codex/config.toml (already configured)";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const section = `\n[mcp_servers.fathom-vault]\ncommand = "npx"\nargs = ["-y", "fathom-mcp"]\n`;
|
|
145
|
+
const separator = content && !content.endsWith("\n") ? "\n" : "";
|
|
146
|
+
fs.writeFileSync(filePath, content + separator + section);
|
|
147
|
+
return ".codex/config.toml";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function writeGeminiJson(cwd) {
|
|
151
|
+
const dir = path.join(cwd, ".gemini");
|
|
152
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
153
|
+
const filePath = path.join(dir, "settings.json");
|
|
154
|
+
const existing = readJsonFile(filePath) || {};
|
|
155
|
+
deepMerge(existing, { mcpServers: { "fathom-vault": MCP_SERVER_ENTRY } });
|
|
156
|
+
writeJsonFile(filePath, existing);
|
|
157
|
+
return ".gemini/settings.json";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writeCursorJson(cwd) {
|
|
161
|
+
const dir = path.join(cwd, ".cursor");
|
|
162
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
163
|
+
const filePath = path.join(dir, "mcp.json");
|
|
164
|
+
const existing = readJsonFile(filePath) || {};
|
|
165
|
+
deepMerge(existing, { mcpServers: { "fathom-vault": MCP_SERVER_ENTRY } });
|
|
166
|
+
writeJsonFile(filePath, existing);
|
|
167
|
+
return ".cursor/mcp.json";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function writeVscodeJson(cwd) {
|
|
171
|
+
const dir = path.join(cwd, ".vscode");
|
|
172
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
173
|
+
const filePath = path.join(dir, "mcp.json");
|
|
174
|
+
const existing = readJsonFile(filePath) || {};
|
|
175
|
+
deepMerge(existing, {
|
|
176
|
+
servers: {
|
|
177
|
+
"fathom-vault": {
|
|
178
|
+
type: "stdio",
|
|
179
|
+
command: "npx",
|
|
180
|
+
args: ["-y", "fathom-mcp"],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
writeJsonFile(filePath, existing);
|
|
185
|
+
return ".vscode/mcp.json";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function writeWindsurfJson(_cwd) {
|
|
189
|
+
const dir = path.join(os.homedir(), ".codeium", "windsurf");
|
|
190
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
191
|
+
const filePath = path.join(dir, "mcp_config.json");
|
|
192
|
+
const existing = readJsonFile(filePath) || {};
|
|
193
|
+
deepMerge(existing, { mcpServers: { "fathom-vault": MCP_SERVER_ENTRY } });
|
|
194
|
+
writeJsonFile(filePath, existing);
|
|
195
|
+
return "~/.codeium/windsurf/mcp_config.json";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const AGENTS = {
|
|
199
|
+
"claude-code": {
|
|
200
|
+
name: "Claude Code",
|
|
201
|
+
detect: (cwd) => fs.existsSync(path.join(cwd, ".claude")),
|
|
202
|
+
configWriter: writeMcpJson,
|
|
203
|
+
hasHooks: true,
|
|
204
|
+
nextSteps: 'Add to CLAUDE.md: `ToolSearch query="+fathom" max_results=20`',
|
|
205
|
+
},
|
|
206
|
+
"codex": {
|
|
207
|
+
name: "OpenAI Codex",
|
|
208
|
+
detect: (cwd) => fs.existsSync(path.join(cwd, ".codex")),
|
|
209
|
+
configWriter: writeCodexToml,
|
|
210
|
+
hasHooks: false,
|
|
211
|
+
nextSteps: "Run `codex` in this directory — fathom tools load automatically.",
|
|
212
|
+
},
|
|
213
|
+
"gemini": {
|
|
214
|
+
name: "Gemini CLI",
|
|
215
|
+
detect: (cwd) => fs.existsSync(path.join(cwd, ".gemini")),
|
|
216
|
+
configWriter: writeGeminiJson,
|
|
217
|
+
hasHooks: false,
|
|
218
|
+
nextSteps: "Run `gemini` in this directory — fathom tools load automatically.",
|
|
219
|
+
},
|
|
220
|
+
"cursor": {
|
|
221
|
+
name: "Cursor",
|
|
222
|
+
detect: (cwd) => fs.existsSync(path.join(cwd, ".cursor")),
|
|
223
|
+
configWriter: writeCursorJson,
|
|
224
|
+
hasHooks: false,
|
|
225
|
+
nextSteps: "Restart Cursor — fathom tools appear in MCP settings.",
|
|
226
|
+
},
|
|
227
|
+
"vscode": {
|
|
228
|
+
name: "VS Code Copilot",
|
|
229
|
+
detect: (cwd) => fs.existsSync(path.join(cwd, ".vscode")),
|
|
230
|
+
configWriter: writeVscodeJson,
|
|
231
|
+
hasHooks: false,
|
|
232
|
+
nextSteps: "Reload VS Code — fathom tools appear in Copilot.",
|
|
233
|
+
},
|
|
234
|
+
"windsurf": {
|
|
235
|
+
name: "Windsurf",
|
|
236
|
+
detect: () => fs.existsSync(path.join(os.homedir(), ".codeium", "windsurf")),
|
|
237
|
+
configWriter: writeWindsurfJson,
|
|
238
|
+
hasHooks: false,
|
|
239
|
+
nextSteps: "Restart Windsurf — fathom tools appear in Cascade.",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Exported for testing
|
|
244
|
+
export { AGENTS, writeMcpJson, writeCodexToml, writeGeminiJson, writeCursorJson, writeVscodeJson, writeWindsurfJson };
|
|
245
|
+
|
|
108
246
|
// --- Init wizard -------------------------------------------------------------
|
|
109
247
|
|
|
110
248
|
async function runInit() {
|
|
@@ -120,10 +258,11 @@ async function runInit() {
|
|
|
120
258
|
hifathom.com · fathom@myrakrusemark.com
|
|
121
259
|
`);
|
|
122
260
|
|
|
123
|
-
// Check for existing config
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
261
|
+
// Check for existing config in *this* directory only (don't walk up —
|
|
262
|
+
// a parent's .fathom.json belongs to a different workspace)
|
|
263
|
+
const localConfigPath = path.join(cwd, ".fathom.json");
|
|
264
|
+
if (fs.existsSync(localConfigPath)) {
|
|
265
|
+
console.log(` Found existing config at: ${localConfigPath}`);
|
|
127
266
|
const proceed = await askYesNo(rl, " Overwrite?", false);
|
|
128
267
|
if (!proceed) {
|
|
129
268
|
console.log(" Aborted.");
|
|
@@ -142,18 +281,57 @@ async function runInit() {
|
|
|
142
281
|
// 3. Description (optional)
|
|
143
282
|
const description = await ask(rl, " Workspace description (optional)", "");
|
|
144
283
|
|
|
145
|
-
//
|
|
146
|
-
const
|
|
284
|
+
// 4. Agent selection — auto-detect and let user choose
|
|
285
|
+
const agentKeys = Object.keys(AGENTS);
|
|
286
|
+
const detected = agentKeys.filter((key) => AGENTS[key].detect(cwd));
|
|
287
|
+
|
|
288
|
+
console.log("\n Detected agents:");
|
|
289
|
+
for (const key of agentKeys) {
|
|
290
|
+
const agent = AGENTS[key];
|
|
291
|
+
const isDetected = detected.includes(key);
|
|
292
|
+
const mark = isDetected ? "✓" : " ";
|
|
293
|
+
const hint = isDetected ? ` (${key === "windsurf" ? "~/.codeium/windsurf/ found" : `.${key === "claude-code" ? "claude" : key === "vscode" ? "vscode" : key}/ found`})` : "";
|
|
294
|
+
console.log(` ${mark} ${agent.name}${hint}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log("\n Configure for which agents?");
|
|
298
|
+
agentKeys.forEach((key, i) => {
|
|
299
|
+
const agent = AGENTS[key];
|
|
300
|
+
const mark = detected.includes(key) ? " ✓" : "";
|
|
301
|
+
console.log(` ${i + 1}. ${agent.name}${mark}`);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const defaultSelection = detected.length > 0
|
|
305
|
+
? detected.map((key) => agentKeys.indexOf(key) + 1).join(",")
|
|
306
|
+
: "1";
|
|
307
|
+
const selectionStr = await ask(rl, "\n Enter numbers, comma-separated", defaultSelection);
|
|
147
308
|
|
|
148
|
-
|
|
149
|
-
|
|
309
|
+
const selectedIndices = selectionStr
|
|
310
|
+
.split(",")
|
|
311
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
312
|
+
.filter((n) => n >= 1 && n <= agentKeys.length);
|
|
313
|
+
const selectedAgents = [...new Set(selectedIndices.map((i) => agentKeys[i - 1]))];
|
|
150
314
|
|
|
151
|
-
|
|
315
|
+
if (selectedAgents.length === 0) {
|
|
316
|
+
console.log(" No agents selected. Defaulting to Claude Code.");
|
|
317
|
+
selectedAgents.push("claude-code");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 5. Server URL
|
|
321
|
+
const serverUrl = await ask(rl, "\n Fathom server URL", "http://localhost:4243");
|
|
322
|
+
|
|
323
|
+
// 6. API key
|
|
152
324
|
const apiKey = await ask(rl, " API key (from dashboard or server first-run output)", "");
|
|
153
325
|
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
326
|
+
// 7. Hooks — only ask if Claude Code is selected
|
|
327
|
+
const hasClaude = selectedAgents.includes("claude-code");
|
|
328
|
+
let enableRecallHook = false;
|
|
329
|
+
let enablePrecompactHook = false;
|
|
330
|
+
if (hasClaude) {
|
|
331
|
+
console.log();
|
|
332
|
+
enableRecallHook = await askYesNo(rl, " Enable vault recall on every message (UserPromptSubmit)?", true);
|
|
333
|
+
enablePrecompactHook = await askYesNo(rl, " Enable PreCompact vault snapshot hook?", true);
|
|
334
|
+
}
|
|
157
335
|
|
|
158
336
|
rl.close();
|
|
159
337
|
|
|
@@ -168,7 +346,7 @@ async function runInit() {
|
|
|
168
346
|
server: serverUrl,
|
|
169
347
|
apiKey,
|
|
170
348
|
description,
|
|
171
|
-
|
|
349
|
+
agents: selectedAgents,
|
|
172
350
|
hooks: {
|
|
173
351
|
"vault-recall": { enabled: enableRecallHook },
|
|
174
352
|
"precompact-snapshot": { enabled: enablePrecompactHook },
|
|
@@ -193,60 +371,53 @@ async function runInit() {
|
|
|
193
371
|
console.log(` · ${vault}/ (already exists)`);
|
|
194
372
|
}
|
|
195
373
|
|
|
196
|
-
//
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"fathom-vault": {
|
|
202
|
-
command: "npx",
|
|
203
|
-
args: ["-y", "fathom-mcp"],
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
writeJsonFile(mcpJsonPath, mcpJson);
|
|
208
|
-
console.log(" ✓ .mcp.json");
|
|
209
|
-
|
|
210
|
-
// .claude/settings.local.json — hook registrations
|
|
211
|
-
const claudeSettingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
212
|
-
const claudeSettings = readJsonFile(claudeSettingsPath) || {};
|
|
213
|
-
|
|
214
|
-
// Claude Code hooks use matcher + hooks array format:
|
|
215
|
-
// { hooks: [{ type: "command", command: "...", timeout: N }] }
|
|
216
|
-
const hooks = {};
|
|
217
|
-
if (enableRecallHook) {
|
|
218
|
-
hooks["UserPromptSubmit"] = [
|
|
219
|
-
...(claudeSettings.hooks?.["UserPromptSubmit"] || []),
|
|
220
|
-
];
|
|
221
|
-
const recallCmd = "bash .fathom/scripts/fathom-recall.sh";
|
|
222
|
-
const hasFathomRecall = hooks["UserPromptSubmit"].some((entry) =>
|
|
223
|
-
entry.hooks?.some((h) => h.command === recallCmd)
|
|
224
|
-
);
|
|
225
|
-
if (!hasFathomRecall) {
|
|
226
|
-
hooks["UserPromptSubmit"].push({
|
|
227
|
-
hooks: [{ type: "command", command: recallCmd, timeout: 10000 }],
|
|
228
|
-
});
|
|
229
|
-
}
|
|
374
|
+
// Per-agent config files
|
|
375
|
+
for (const agentKey of selectedAgents) {
|
|
376
|
+
const agent = AGENTS[agentKey];
|
|
377
|
+
const result = agent.configWriter(cwd);
|
|
378
|
+
console.log(` ✓ ${result}`);
|
|
230
379
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
380
|
+
|
|
381
|
+
// Claude Code hooks — only if claude-code is selected
|
|
382
|
+
if (hasClaude && (enableRecallHook || enablePrecompactHook)) {
|
|
383
|
+
const claudeSettingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
384
|
+
const claudeSettings = readJsonFile(claudeSettingsPath) || {};
|
|
385
|
+
|
|
386
|
+
const hooks = {};
|
|
387
|
+
if (enableRecallHook) {
|
|
388
|
+
hooks["UserPromptSubmit"] = [
|
|
389
|
+
...(claudeSettings.hooks?.["UserPromptSubmit"] || []),
|
|
390
|
+
];
|
|
391
|
+
const recallCmd = "bash .fathom/scripts/fathom-recall.sh";
|
|
392
|
+
const hasFathomRecall = hooks["UserPromptSubmit"].some((entry) =>
|
|
393
|
+
entry.hooks?.some((h) => h.command === recallCmd)
|
|
394
|
+
);
|
|
395
|
+
if (!hasFathomRecall) {
|
|
396
|
+
hooks["UserPromptSubmit"].push({
|
|
397
|
+
hooks: [{ type: "command", command: recallCmd, timeout: 10000 }],
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (enablePrecompactHook) {
|
|
402
|
+
hooks["PreCompact"] = [
|
|
403
|
+
...(claudeSettings.hooks?.["PreCompact"] || []),
|
|
404
|
+
];
|
|
405
|
+
const precompactCmd = "bash .fathom/scripts/fathom-precompact.sh";
|
|
406
|
+
const hasFathomPrecompact = hooks["PreCompact"].some((entry) =>
|
|
407
|
+
entry.hooks?.some((h) => h.command === precompactCmd)
|
|
408
|
+
);
|
|
409
|
+
if (!hasFathomPrecompact) {
|
|
410
|
+
hooks["PreCompact"].push({
|
|
411
|
+
hooks: [{ type: "command", command: precompactCmd, timeout: 30000 }],
|
|
412
|
+
});
|
|
413
|
+
}
|
|
243
414
|
}
|
|
244
|
-
}
|
|
245
415
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
416
|
+
if (Object.keys(hooks).length > 0) {
|
|
417
|
+
claudeSettings.hooks = { ...(claudeSettings.hooks || {}), ...hooks };
|
|
418
|
+
writeJsonFile(claudeSettingsPath, claudeSettings);
|
|
419
|
+
console.log(" ✓ .claude/settings.local.json (hooks)");
|
|
420
|
+
}
|
|
250
421
|
}
|
|
251
422
|
|
|
252
423
|
// .gitignore
|
|
@@ -258,7 +429,11 @@ async function runInit() {
|
|
|
258
429
|
const regClient = createClient({ server: serverUrl, apiKey, workspace });
|
|
259
430
|
const isUp = await regClient.healthCheck();
|
|
260
431
|
if (isUp) {
|
|
261
|
-
const regResult = await regClient.registerWorkspace(workspace, cwd, {
|
|
432
|
+
const regResult = await regClient.registerWorkspace(workspace, cwd, {
|
|
433
|
+
vault,
|
|
434
|
+
description,
|
|
435
|
+
agents: selectedAgents,
|
|
436
|
+
});
|
|
262
437
|
if (regResult.ok) {
|
|
263
438
|
console.log(` ✓ Registered workspace "${workspace}" with server`);
|
|
264
439
|
} else if (regResult.error) {
|
|
@@ -267,17 +442,15 @@ async function runInit() {
|
|
|
267
442
|
}
|
|
268
443
|
}
|
|
269
444
|
|
|
270
|
-
|
|
271
|
-
Done! Fathom MCP is configured for workspace "${workspace}"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Load fathom tools on startup: \`ToolSearch query="+fathom" max_results=20\`
|
|
280
|
-
`);
|
|
445
|
+
// Per-agent next steps
|
|
446
|
+
console.log(`\n Done! Fathom MCP is configured for workspace "${workspace}".`);
|
|
447
|
+
console.log("\n Next steps:");
|
|
448
|
+
console.log(" 1. Start the server: cd fathom-server && python app.py");
|
|
449
|
+
for (const agentKey of selectedAgents) {
|
|
450
|
+
const agent = AGENTS[agentKey];
|
|
451
|
+
console.log(` · ${agent.name}: ${agent.nextSteps}`);
|
|
452
|
+
}
|
|
453
|
+
console.log();
|
|
281
454
|
}
|
|
282
455
|
|
|
283
456
|
// --- Status command ----------------------------------------------------------
|
|
@@ -292,6 +465,7 @@ async function runStatus() {
|
|
|
292
465
|
console.log(` Vault: ${config.vault}`);
|
|
293
466
|
console.log(` Server: ${config.server}`);
|
|
294
467
|
console.log(` API Key: ${config.apiKey ? config.apiKey.slice(0, 7) + "..." + config.apiKey.slice(-4) : "(not set)"}`);
|
|
468
|
+
console.log(` Agents: ${config.agents.length > 0 ? config.agents.join(", ") : "(none)"}`);
|
|
295
469
|
|
|
296
470
|
// Check vault directory
|
|
297
471
|
const vaultExists = fs.existsSync(config.vault);
|
|
@@ -307,8 +481,15 @@ async function runStatus() {
|
|
|
307
481
|
const names = Object.keys(wsResult.profiles);
|
|
308
482
|
console.log(` Workspaces: ${names.join(", ") || "(none)"}`);
|
|
309
483
|
for (const [name, profile] of Object.entries(wsResult.profiles)) {
|
|
310
|
-
|
|
311
|
-
|
|
484
|
+
if (profile.type === "human") {
|
|
485
|
+
console.log(` ${name}: human`);
|
|
486
|
+
} else {
|
|
487
|
+
const agentLabel = profile.agents?.length > 0
|
|
488
|
+
? ` [${profile.agents.join(", ")}]`
|
|
489
|
+
: profile.architecture ? ` [${profile.architecture}]` : "";
|
|
490
|
+
const runStatus = profile.running ? "running" : "stopped";
|
|
491
|
+
console.log(` ${name}: ${runStatus}${agentLabel}`);
|
|
492
|
+
}
|
|
312
493
|
}
|
|
313
494
|
}
|
|
314
495
|
}
|
|
@@ -318,23 +499,30 @@ async function runStatus() {
|
|
|
318
499
|
|
|
319
500
|
// --- Main --------------------------------------------------------------------
|
|
320
501
|
|
|
321
|
-
|
|
502
|
+
// Guard: only run CLI when this module is the entry point (not when imported by tests)
|
|
503
|
+
const isMain = process.argv[1] && (
|
|
504
|
+
process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("fathom-mcp")
|
|
505
|
+
);
|
|
322
506
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
507
|
+
if (isMain) {
|
|
508
|
+
const command = process.argv[2];
|
|
509
|
+
|
|
510
|
+
if (command === "init") {
|
|
511
|
+
runInit().catch((e) => {
|
|
512
|
+
console.error(`Error: ${e.message}`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
});
|
|
515
|
+
} else if (command === "status") {
|
|
516
|
+
runStatus().catch((e) => {
|
|
517
|
+
console.error(`Error: ${e.message}`);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
});
|
|
520
|
+
} else if (!command || command === "serve") {
|
|
521
|
+
// Default: start MCP server
|
|
522
|
+
import("./index.js");
|
|
523
|
+
} else {
|
|
524
|
+
console.error(`Unknown command: ${command}`);
|
|
525
|
+
console.error("Usage: fathom-mcp [init|status|serve]");
|
|
331
526
|
process.exit(1);
|
|
332
|
-
}
|
|
333
|
-
} else if (!command || command === "serve") {
|
|
334
|
-
// Default: start MCP server
|
|
335
|
-
import("./index.js");
|
|
336
|
-
} else {
|
|
337
|
-
console.error(`Unknown command: ${command}`);
|
|
338
|
-
console.error("Usage: fathom-mcp [init|status|serve]");
|
|
339
|
-
process.exit(1);
|
|
527
|
+
}
|
|
340
528
|
}
|
package/src/config.js
CHANGED
|
@@ -18,7 +18,7 @@ const DEFAULTS = {
|
|
|
18
18
|
server: "http://localhost:4243",
|
|
19
19
|
apiKey: "",
|
|
20
20
|
description: "",
|
|
21
|
-
|
|
21
|
+
agents: [],
|
|
22
22
|
hooks: {
|
|
23
23
|
"context-inject": { enabled: true },
|
|
24
24
|
"precompact-snapshot": { enabled: true },
|
|
@@ -68,7 +68,12 @@ export function resolveConfig(startDir = process.cwd()) {
|
|
|
68
68
|
if (config.server) result.server = config.server;
|
|
69
69
|
if (config.apiKey) result.apiKey = config.apiKey;
|
|
70
70
|
if (config.description) result.description = config.description;
|
|
71
|
-
|
|
71
|
+
// Backward compat: migrate legacy `architecture` string to `agents` array
|
|
72
|
+
if (config.agents && Array.isArray(config.agents)) {
|
|
73
|
+
result.agents = config.agents;
|
|
74
|
+
} else if (config.architecture) {
|
|
75
|
+
result.agents = [config.architecture];
|
|
76
|
+
}
|
|
72
77
|
if (config.hooks) {
|
|
73
78
|
result.hooks = { ...result.hooks, ...config.hooks };
|
|
74
79
|
}
|
|
@@ -113,7 +118,7 @@ export function writeConfig(dir, config) {
|
|
|
113
118
|
server: config.server || DEFAULTS.server,
|
|
114
119
|
apiKey: config.apiKey || "",
|
|
115
120
|
description: config.description || "",
|
|
116
|
-
|
|
121
|
+
agents: config.agents || [],
|
|
117
122
|
hooks: config.hooks || DEFAULTS.hooks,
|
|
118
123
|
};
|
|
119
124
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
package/src/server-client.js
CHANGED
|
@@ -104,11 +104,13 @@ export function createClient(config) {
|
|
|
104
104
|
return request("GET", "/api/workspaces/profiles");
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async function registerWorkspace(name, projectPath, { vault, description, architecture } = {}) {
|
|
107
|
+
async function registerWorkspace(name, projectPath, { vault, description, agents, architecture } = {}) {
|
|
108
108
|
const body = { name, path: projectPath };
|
|
109
109
|
if (vault) body.vault = vault;
|
|
110
110
|
if (description) body.description = description;
|
|
111
|
-
if (
|
|
111
|
+
if (agents && agents.length > 0) body.agents = agents;
|
|
112
|
+
// Legacy fallback
|
|
113
|
+
if (architecture && !agents?.length) body.architecture = architecture;
|
|
112
114
|
return request("POST", "/api/workspaces", { body });
|
|
113
115
|
}
|
|
114
116
|
|