fathom-mcp 0.1.4 → 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 +284 -92
- package/src/config.js +8 -0
- package/src/index.js +1 -0
- package/src/server-client.js +4 -1
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,15 +281,57 @@ async function runInit() {
|
|
|
142
281
|
// 3. Description (optional)
|
|
143
282
|
const description = await ask(rl, " Workspace description (optional)", "");
|
|
144
283
|
|
|
145
|
-
// 4.
|
|
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);
|
|
308
|
+
|
|
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]))];
|
|
147
314
|
|
|
148
|
-
|
|
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
|
|
149
324
|
const apiKey = await ask(rl, " API key (from dashboard or server first-run output)", "");
|
|
150
325
|
|
|
151
|
-
//
|
|
152
|
-
const
|
|
153
|
-
|
|
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
|
+
}
|
|
154
335
|
|
|
155
336
|
rl.close();
|
|
156
337
|
|
|
@@ -165,6 +346,7 @@ async function runInit() {
|
|
|
165
346
|
server: serverUrl,
|
|
166
347
|
apiKey,
|
|
167
348
|
description,
|
|
349
|
+
agents: selectedAgents,
|
|
168
350
|
hooks: {
|
|
169
351
|
"vault-recall": { enabled: enableRecallHook },
|
|
170
352
|
"precompact-snapshot": { enabled: enablePrecompactHook },
|
|
@@ -189,60 +371,53 @@ async function runInit() {
|
|
|
189
371
|
console.log(` · ${vault}/ (already exists)`);
|
|
190
372
|
}
|
|
191
373
|
|
|
192
|
-
//
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"fathom-vault": {
|
|
198
|
-
command: "npx",
|
|
199
|
-
args: ["-y", "fathom-mcp"],
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
writeJsonFile(mcpJsonPath, mcpJson);
|
|
204
|
-
console.log(" ✓ .mcp.json");
|
|
205
|
-
|
|
206
|
-
// .claude/settings.local.json — hook registrations
|
|
207
|
-
const claudeSettingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
208
|
-
const claudeSettings = readJsonFile(claudeSettingsPath) || {};
|
|
209
|
-
|
|
210
|
-
// Claude Code hooks use matcher + hooks array format:
|
|
211
|
-
// { hooks: [{ type: "command", command: "...", timeout: N }] }
|
|
212
|
-
const hooks = {};
|
|
213
|
-
if (enableRecallHook) {
|
|
214
|
-
hooks["UserPromptSubmit"] = [
|
|
215
|
-
...(claudeSettings.hooks?.["UserPromptSubmit"] || []),
|
|
216
|
-
];
|
|
217
|
-
const recallCmd = "bash .fathom/scripts/fathom-recall.sh";
|
|
218
|
-
const hasFathomRecall = hooks["UserPromptSubmit"].some((entry) =>
|
|
219
|
-
entry.hooks?.some((h) => h.command === recallCmd)
|
|
220
|
-
);
|
|
221
|
-
if (!hasFathomRecall) {
|
|
222
|
-
hooks["UserPromptSubmit"].push({
|
|
223
|
-
hooks: [{ type: "command", command: recallCmd, timeout: 10000 }],
|
|
224
|
-
});
|
|
225
|
-
}
|
|
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}`);
|
|
226
379
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
+
}
|
|
239
414
|
}
|
|
240
|
-
}
|
|
241
415
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
}
|
|
246
421
|
}
|
|
247
422
|
|
|
248
423
|
// .gitignore
|
|
@@ -254,7 +429,11 @@ async function runInit() {
|
|
|
254
429
|
const regClient = createClient({ server: serverUrl, apiKey, workspace });
|
|
255
430
|
const isUp = await regClient.healthCheck();
|
|
256
431
|
if (isUp) {
|
|
257
|
-
const regResult = await regClient.registerWorkspace(workspace, cwd, {
|
|
432
|
+
const regResult = await regClient.registerWorkspace(workspace, cwd, {
|
|
433
|
+
vault,
|
|
434
|
+
description,
|
|
435
|
+
agents: selectedAgents,
|
|
436
|
+
});
|
|
258
437
|
if (regResult.ok) {
|
|
259
438
|
console.log(` ✓ Registered workspace "${workspace}" with server`);
|
|
260
439
|
} else if (regResult.error) {
|
|
@@ -263,17 +442,15 @@ async function runInit() {
|
|
|
263
442
|
}
|
|
264
443
|
}
|
|
265
444
|
|
|
266
|
-
|
|
267
|
-
Done! Fathom MCP is configured for workspace "${workspace}"
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
Load fathom tools on startup: \`ToolSearch query="+fathom" max_results=20\`
|
|
276
|
-
`);
|
|
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();
|
|
277
454
|
}
|
|
278
455
|
|
|
279
456
|
// --- Status command ----------------------------------------------------------
|
|
@@ -288,6 +465,7 @@ async function runStatus() {
|
|
|
288
465
|
console.log(` Vault: ${config.vault}`);
|
|
289
466
|
console.log(` Server: ${config.server}`);
|
|
290
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)"}`);
|
|
291
469
|
|
|
292
470
|
// Check vault directory
|
|
293
471
|
const vaultExists = fs.existsSync(config.vault);
|
|
@@ -303,8 +481,15 @@ async function runStatus() {
|
|
|
303
481
|
const names = Object.keys(wsResult.profiles);
|
|
304
482
|
console.log(` Workspaces: ${names.join(", ") || "(none)"}`);
|
|
305
483
|
for (const [name, profile] of Object.entries(wsResult.profiles)) {
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
493
|
}
|
|
309
494
|
}
|
|
310
495
|
}
|
|
@@ -314,23 +499,30 @@ async function runStatus() {
|
|
|
314
499
|
|
|
315
500
|
// --- Main --------------------------------------------------------------------
|
|
316
501
|
|
|
317
|
-
|
|
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
|
+
);
|
|
318
506
|
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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]");
|
|
327
526
|
process.exit(1);
|
|
328
|
-
}
|
|
329
|
-
} else if (!command || command === "serve") {
|
|
330
|
-
// Default: start MCP server
|
|
331
|
-
import("./index.js");
|
|
332
|
-
} else {
|
|
333
|
-
console.error(`Unknown command: ${command}`);
|
|
334
|
-
console.error("Usage: fathom-mcp [init|status|serve]");
|
|
335
|
-
process.exit(1);
|
|
527
|
+
}
|
|
336
528
|
}
|
package/src/config.js
CHANGED
|
@@ -18,6 +18,7 @@ const DEFAULTS = {
|
|
|
18
18
|
server: "http://localhost:4243",
|
|
19
19
|
apiKey: "",
|
|
20
20
|
description: "",
|
|
21
|
+
agents: [],
|
|
21
22
|
hooks: {
|
|
22
23
|
"context-inject": { enabled: true },
|
|
23
24
|
"precompact-snapshot": { enabled: true },
|
|
@@ -67,6 +68,12 @@ export function resolveConfig(startDir = process.cwd()) {
|
|
|
67
68
|
if (config.server) result.server = config.server;
|
|
68
69
|
if (config.apiKey) result.apiKey = config.apiKey;
|
|
69
70
|
if (config.description) result.description = config.description;
|
|
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
|
+
}
|
|
70
77
|
if (config.hooks) {
|
|
71
78
|
result.hooks = { ...result.hooks, ...config.hooks };
|
|
72
79
|
}
|
|
@@ -111,6 +118,7 @@ export function writeConfig(dir, config) {
|
|
|
111
118
|
server: config.server || DEFAULTS.server,
|
|
112
119
|
apiKey: config.apiKey || "",
|
|
113
120
|
description: config.description || "",
|
|
121
|
+
agents: config.agents || [],
|
|
114
122
|
hooks: config.hooks || DEFAULTS.hooks,
|
|
115
123
|
};
|
|
116
124
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
package/src/index.js
CHANGED
package/src/server-client.js
CHANGED
|
@@ -104,10 +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 } = {}) {
|
|
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 (agents && agents.length > 0) body.agents = agents;
|
|
112
|
+
// Legacy fallback
|
|
113
|
+
if (architecture && !agents?.length) body.architecture = architecture;
|
|
111
114
|
return request("POST", "/api/workspaces", { body });
|
|
112
115
|
}
|
|
113
116
|
|