fathom-mcp 0.1.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 ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-02-25)
4
+
5
+ Initial release.
6
+
7
+ - 16 MCP tools: vault read/write/append, image ops, search (BM25/vector/hybrid), rooms, workspaces
8
+ - CLI: `npx fathom-mcp init` setup wizard, `npx fathom-mcp status`
9
+ - Config resolution: `.fathom.json` → env vars → defaults
10
+ - Hook scripts: SessionStart context injection, PreCompact vault snapshot
11
+ - Direct file I/O for vault operations (no server needed for reads/writes)
12
+ - HTTP client for fathom-server API (search, rooms, workspaces)
13
+ - API key auth support (Bearer token)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Myra Krusemark
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,118 @@
1
+ # fathom-mcp
2
+
3
+ ```
4
+ ▐▘ ▗ ▌
5
+ ▜▘▀▌▜▘▛▌▛▌▛▛▌▄▖▛▛▌▛▘▛▌
6
+ ▐ █▌▐▖▌▌▙▌▌▌▌ ▌▌▌▙▖▙▌
7
+
8
+
9
+ hifathom.com · fathom@myrakrusemark.com
10
+ ```
11
+
12
+ MCP server for [Fathom](https://hifathom.com) — vault operations, search, rooms, and cross-workspace communication.
13
+
14
+ The MCP tools that let Claude Code interact with your vault. Reads/writes happen locally (fast, no network hop). Search, rooms, and workspace management go through your [fathom-server](https://github.com/myrakrusemark/fathom-vault) instance.
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ npx fathom-mcp init
20
+ ```
21
+
22
+ That's it. Restart Claude Code and fathom tools will be available.
23
+
24
+ The init wizard creates:
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
29
+
30
+ ## Prerequisites
31
+
32
+ - **Node.js 18+**
33
+ - **[fathom-server](https://github.com/myrakrusemark/fathom-vault)** running (for search, rooms, and workspace features)
34
+
35
+ ## Commands
36
+
37
+ ```bash
38
+ npx fathom-mcp # Start MCP server (stdio — used by .mcp.json)
39
+ npx fathom-mcp init # Interactive setup wizard
40
+ npx fathom-mcp status # Check server connection + workspace status
41
+ ```
42
+
43
+ ## Tools
44
+
45
+ ### Local (direct file I/O)
46
+ | Tool | Description |
47
+ |------|-------------|
48
+ | `fathom_vault_read` | Read a vault file with parsed frontmatter |
49
+ | `fathom_vault_write` | Create or overwrite a vault file (validates frontmatter) |
50
+ | `fathom_vault_append` | Append to a vault file (auto-creates with frontmatter if new) |
51
+ | `fathom_vault_image` | Read a vault image as base64 |
52
+ | `fathom_vault_write_asset` | Save a base64 image to a vault folder's assets/ |
53
+
54
+ ### Server (via fathom-server API)
55
+ | Tool | Description |
56
+ |------|-------------|
57
+ | `fathom_vault_list` | List vault folders with activity signals |
58
+ | `fathom_vault_folder` | List files in a folder with metadata and previews |
59
+ | `fathom_vault_search` | BM25 keyword search |
60
+ | `fathom_vault_vsearch` | Semantic/vector search |
61
+ | `fathom_vault_query` | Hybrid search (BM25 + vectors + reranking) |
62
+ | `fathom_room_post` | Post to a shared room (supports @mentions) |
63
+ | `fathom_room_read` | Read recent room messages |
64
+ | `fathom_room_list` | List all rooms |
65
+ | `fathom_room_describe` | Set a room's description/topic |
66
+ | `fathom_workspaces` | List all configured workspaces |
67
+ | `fathom_send` | Send a message to another workspace's Claude instance |
68
+
69
+ ## Configuration
70
+
71
+ ### `.fathom.json`
72
+
73
+ ```json
74
+ {
75
+ "workspace": "my-project",
76
+ "vault": "vault",
77
+ "server": "http://localhost:4243",
78
+ "apiKey": "fv_abc123...",
79
+ "hooks": {
80
+ "context-inject": { "enabled": true },
81
+ "precompact-snapshot": { "enabled": true }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Resolution order (highest priority first)
87
+
88
+ 1. Environment variables: `FATHOM_SERVER_URL`, `FATHOM_API_KEY`, `FATHOM_WORKSPACE`, `FATHOM_VAULT_DIR`
89
+ 2. `.fathom.json` (walked up from cwd to filesystem root)
90
+ 3. Built-in defaults
91
+
92
+ ## Hooks
93
+
94
+ **SessionStart / UserPromptSubmit** (`fathom-context.sh`): Injects recent vault activity into Claude's context.
95
+
96
+ **PreCompact** (`fathom-precompact.sh`): Records which vault files were active in the session before context compaction.
97
+
98
+ ## Vault Frontmatter Schema
99
+
100
+ Files can optionally include YAML frontmatter:
101
+
102
+ ```yaml
103
+ ---
104
+ title: My Note # required (string)
105
+ date: 2026-02-25 # required (string, YYYY-MM-DD)
106
+ tags: # optional (list)
107
+ - research
108
+ - identity
109
+ status: draft # optional: draft | published | archived
110
+ project: my-project # optional (string)
111
+ aliases: # optional (list)
112
+ - alt-name
113
+ ---
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "fathom-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
+ "type": "module",
6
+ "bin": {
7
+ "fathom-mcp": "src/cli.js"
8
+ },
9
+ "main": "src/index.js",
10
+ "files": [
11
+ "src/",
12
+ "scripts/",
13
+ "README.md",
14
+ "CHANGELOG.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "start": "node src/index.js",
19
+ "test": "node --test 'test/*.test.js'",
20
+ "lint": "eslint src/ test/",
21
+ "lint:fix": "eslint --fix src/ test/"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.26.0",
25
+ "js-yaml": "^4.1.0"
26
+ },
27
+ "devDependencies": {
28
+ "@eslint/js": "^9.39.3",
29
+ "eslint": "^9.0.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "license": "MIT",
35
+ "keywords": [
36
+ "mcp",
37
+ "vault",
38
+ "fathom",
39
+ "claude",
40
+ "ai-agent",
41
+ "memory"
42
+ ]
43
+ }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # Fathom SessionStart/UserPromptSubmit hook — injects vault context.
3
+ #
4
+ # Reads .fathom.json to find vault path and server URL.
5
+ # On SessionStart: injects recent heartbeat + active vault folders.
6
+ # On UserPromptSubmit: searches vault for relevant context.
7
+
8
+ set -euo pipefail
9
+
10
+ # Walk up to find .fathom.json
11
+ find_config() {
12
+ local dir="$PWD"
13
+ while [ "$dir" != "/" ]; do
14
+ if [ -f "$dir/.fathom.json" ]; then
15
+ echo "$dir/.fathom.json"
16
+ return 0
17
+ fi
18
+ dir="$(dirname "$dir")"
19
+ done
20
+ return 1
21
+ }
22
+
23
+ CONFIG_FILE=$(find_config 2>/dev/null) || exit 0
24
+
25
+ # Extract config values
26
+ WORKSPACE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('workspace',''))" 2>/dev/null || echo "")
27
+ SERVER=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('server','http://localhost:4243'))" 2>/dev/null || echo "http://localhost:4243")
28
+ API_KEY=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('apiKey',''))" 2>/dev/null || echo "")
29
+ HOOK_ENABLED=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('hooks',{}).get('context-inject',{}).get('enabled','true'))" 2>/dev/null || echo "true")
30
+
31
+ if [ "$HOOK_ENABLED" != "True" ] && [ "$HOOK_ENABLED" != "true" ]; then
32
+ exit 0
33
+ fi
34
+
35
+ # Read user prompt from stdin (if UserPromptSubmit)
36
+ INPUT=$(cat)
37
+
38
+ # Build auth header
39
+ AUTH_HEADER=""
40
+ if [ -n "$API_KEY" ]; then
41
+ AUTH_HEADER="Authorization: Bearer $API_KEY"
42
+ fi
43
+
44
+ # Try to get recent vault activity from server
45
+ CONTEXT=""
46
+ if [ -n "$SERVER" ]; then
47
+ ACTIVITY=$(curl -sf -H "$AUTH_HEADER" \
48
+ "${SERVER}/api/vault/activity?workspace=${WORKSPACE}&limit=5" 2>/dev/null || echo "")
49
+ if [ -n "$ACTIVITY" ]; then
50
+ CONTEXT="Recent vault activity:\n$ACTIVITY"
51
+ fi
52
+ fi
53
+
54
+ if [ -n "$CONTEXT" ]; then
55
+ # Output as hook response
56
+ python3 -c "
57
+ import json, sys
58
+ result = {
59
+ 'hookSpecificOutput': {
60
+ 'additionalContext': '''$CONTEXT'''
61
+ }
62
+ }
63
+ json.dump(result, sys.stdout)
64
+ "
65
+ fi
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # Fathom PreCompact hook — snapshots vault state before context compaction.
3
+ #
4
+ # Reads the transcript, extracts any vault file paths mentioned,
5
+ # and records which files were active in this session for continuity.
6
+
7
+ set -euo pipefail
8
+
9
+ # Walk up to find .fathom.json
10
+ find_config() {
11
+ local dir="$PWD"
12
+ while [ "$dir" != "/" ]; do
13
+ if [ -f "$dir/.fathom.json" ]; then
14
+ echo "$dir/.fathom.json"
15
+ return 0
16
+ fi
17
+ dir="$(dirname "$dir")"
18
+ done
19
+ return 1
20
+ }
21
+
22
+ CONFIG_FILE=$(find_config 2>/dev/null) || exit 0
23
+
24
+ WORKSPACE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('workspace',''))" 2>/dev/null || echo "")
25
+ SERVER=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('server','http://localhost:4243'))" 2>/dev/null || echo "http://localhost:4243")
26
+ API_KEY=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('apiKey',''))" 2>/dev/null || echo "")
27
+ HOOK_ENABLED=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('hooks',{}).get('precompact-snapshot',{}).get('enabled','true'))" 2>/dev/null || echo "true")
28
+
29
+ if [ "$HOOK_ENABLED" != "True" ] && [ "$HOOK_ENABLED" != "true" ]; then
30
+ exit 0
31
+ fi
32
+
33
+ # Read PreCompact input (contains transcript_path)
34
+ INPUT=$(cat)
35
+ TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null || echo "")
36
+
37
+ if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
38
+ exit 0
39
+ fi
40
+
41
+ # Extract vault file paths from transcript
42
+ VAULT_FILES=$(grep -oP 'vault/[a-zA-Z0-9_/.-]+\.md' "$TRANSCRIPT_PATH" 2>/dev/null | sort -u || echo "")
43
+
44
+ if [ -z "$VAULT_FILES" ]; then
45
+ exit 0
46
+ fi
47
+
48
+ # Notify server about accessed files (best-effort)
49
+ AUTH_HEADER=""
50
+ if [ -n "$API_KEY" ]; then
51
+ AUTH_HEADER="Authorization: Bearer $API_KEY"
52
+ fi
53
+
54
+ for FILE in $VAULT_FILES; do
55
+ curl -sf -X POST -H "Content-Type: application/json" -H "$AUTH_HEADER" \
56
+ -d "{\"path\": \"$FILE\"}" \
57
+ "${SERVER}/api/vault/access?workspace=${WORKSPACE}" >/dev/null 2>&1 || true
58
+ done
59
+
60
+ # Output summary
61
+ FILE_COUNT=$(echo "$VAULT_FILES" | wc -l)
62
+ python3 -c "
63
+ import json, sys
64
+ result = {
65
+ 'systemMessage': 'Fathom: Recorded ${FILE_COUNT} vault file(s) from this session.'
66
+ }
67
+ json.dump(result, sys.stdout)
68
+ "
package/src/cli.js ADDED
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * fathom-mcp CLI
5
+ *
6
+ * Usage:
7
+ * npx fathom-mcp — Start MCP server (stdio, for .mcp.json)
8
+ * npx fathom-mcp init — Interactive setup wizard
9
+ * npx fathom-mcp status — Check server connection + workspace status
10
+ */
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import readline from "readline";
15
+ import { fileURLToPath } from "url";
16
+
17
+ import { resolveConfig, writeConfig, findConfigFile } from "./config.js";
18
+ import { createClient } from "./server-client.js";
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const SCRIPTS_DIR = path.join(__dirname, "..", "scripts");
22
+
23
+ // --- Helpers -----------------------------------------------------------------
24
+
25
+ function ask(rl, question, defaultVal = "") {
26
+ const suffix = defaultVal ? ` (${defaultVal})` : "";
27
+ return new Promise((resolve) => {
28
+ rl.question(`${question}${suffix}: `, (answer) => {
29
+ resolve(answer.trim() || defaultVal);
30
+ });
31
+ });
32
+ }
33
+
34
+ function askYesNo(rl, question, defaultYes = true) {
35
+ const hint = defaultYes ? "Y/n" : "y/N";
36
+ return new Promise((resolve) => {
37
+ rl.question(`${question} [${hint}]: `, (answer) => {
38
+ const a = answer.trim().toLowerCase();
39
+ if (!a) return resolve(defaultYes);
40
+ resolve(a === "y" || a === "yes");
41
+ });
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Deep merge obj2 into obj1 (mutates obj1). Arrays are replaced, not merged.
47
+ */
48
+ function deepMerge(obj1, obj2) {
49
+ for (const key of Object.keys(obj2)) {
50
+ if (
51
+ obj1[key] &&
52
+ typeof obj1[key] === "object" &&
53
+ !Array.isArray(obj1[key]) &&
54
+ typeof obj2[key] === "object" &&
55
+ !Array.isArray(obj2[key])
56
+ ) {
57
+ deepMerge(obj1[key], obj2[key]);
58
+ } else {
59
+ obj1[key] = obj2[key];
60
+ }
61
+ }
62
+ return obj1;
63
+ }
64
+
65
+ function readJsonFile(filePath) {
66
+ try {
67
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ function writeJsonFile(filePath, data) {
74
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
75
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
76
+ }
77
+
78
+ function appendToGitignore(dir, patterns) {
79
+ const gitignorePath = path.join(dir, ".gitignore");
80
+ let existing = "";
81
+ try {
82
+ existing = fs.readFileSync(gitignorePath, "utf-8");
83
+ } catch { /* file doesn't exist */ }
84
+
85
+ const missing = patterns.filter((p) => !existing.includes(p));
86
+ if (missing.length > 0) {
87
+ const suffix = existing.endsWith("\n") || !existing ? "" : "\n";
88
+ fs.appendFileSync(gitignorePath, suffix + missing.join("\n") + "\n");
89
+ }
90
+ }
91
+
92
+ function copyScripts(targetDir) {
93
+ fs.mkdirSync(targetDir, { recursive: true });
94
+ try {
95
+ const files = fs.readdirSync(SCRIPTS_DIR);
96
+ for (const file of files) {
97
+ const src = path.join(SCRIPTS_DIR, file);
98
+ const dest = path.join(targetDir, file);
99
+ fs.copyFileSync(src, dest);
100
+ fs.chmodSync(dest, 0o755);
101
+ }
102
+ return files;
103
+ } catch {
104
+ return [];
105
+ }
106
+ }
107
+
108
+ // --- Init wizard -------------------------------------------------------------
109
+
110
+ async function runInit() {
111
+ const cwd = process.cwd();
112
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
113
+
114
+ console.log(`
115
+ ▐▘ ▗ ▌
116
+ ▜▘▀▌▜▘▛▌▛▌▛▛▌▄▖▛▛▌▛▘▛▌
117
+ ▐ █▌▐▖▌▌▙▌▌▌▌ ▌▌▌▙▖▙▌
118
+
119
+
120
+ hifathom.com · fathom@myrakrusemark.com
121
+ `);
122
+
123
+ // Check for existing config
124
+ const existing = findConfigFile(cwd);
125
+ if (existing) {
126
+ console.log(` Found existing config at: ${existing.path}`);
127
+ const proceed = await askYesNo(rl, " Overwrite?", false);
128
+ if (!proceed) {
129
+ console.log(" Aborted.");
130
+ rl.close();
131
+ process.exit(0);
132
+ }
133
+ }
134
+
135
+ // 1. Workspace name
136
+ const defaultName = path.basename(cwd);
137
+ const workspace = await ask(rl, " Workspace name", defaultName);
138
+
139
+ // 2. Vault subdirectory
140
+ const vault = await ask(rl, " Vault subdirectory", "vault");
141
+
142
+ // 3. Server URL
143
+ const serverUrl = await ask(rl, " Fathom server URL", "http://localhost:4243");
144
+
145
+ // 4. API key
146
+ let apiKey = "";
147
+ const tryFetch = await askYesNo(rl, " Fetch API key from server?", true);
148
+ if (tryFetch) {
149
+ console.log(" Connecting to server...");
150
+ const tmpClient = createClient({ server: serverUrl, apiKey: "", workspace });
151
+ const isUp = await tmpClient.healthCheck();
152
+ if (isUp) {
153
+ const keyResp = await tmpClient.getApiKey();
154
+ if (keyResp.api_key) {
155
+ apiKey = keyResp.api_key;
156
+ console.log(` Got API key: ${apiKey.slice(0, 7)}...${apiKey.slice(-4)}`);
157
+ } else {
158
+ console.log(" Could not fetch key (auth may not be configured yet).");
159
+ }
160
+ } else {
161
+ console.log(" Server not reachable. You can add the API key to .fathom.json later.");
162
+ }
163
+ }
164
+ if (!apiKey) {
165
+ apiKey = await ask(rl, " API key (or leave blank)", "");
166
+ }
167
+
168
+ // 5. Hooks
169
+ const enableContextHook = await askYesNo(rl, " Enable SessionStart context injection hook?", true);
170
+ const enablePrecompactHook = await askYesNo(rl, " Enable PreCompact vault snapshot hook?", true);
171
+
172
+ rl.close();
173
+
174
+ // --- Write files ---
175
+
176
+ console.log("\n Creating files...\n");
177
+
178
+ // .fathom.json
179
+ const configData = {
180
+ workspace,
181
+ vault,
182
+ server: serverUrl,
183
+ apiKey,
184
+ hooks: {
185
+ "context-inject": { enabled: enableContextHook },
186
+ "precompact-snapshot": { enabled: enablePrecompactHook },
187
+ },
188
+ };
189
+ const configPath = writeConfig(cwd, configData);
190
+ console.log(` ✓ ${path.relative(cwd, configPath)}`);
191
+
192
+ // .fathom/scripts/
193
+ const scriptsDir = path.join(cwd, ".fathom", "scripts");
194
+ const copiedScripts = copyScripts(scriptsDir);
195
+ if (copiedScripts.length > 0) {
196
+ console.log(` ✓ .fathom/scripts/ (${copiedScripts.length} scripts)`);
197
+ }
198
+
199
+ // vault/ directory
200
+ const vaultDir = path.join(cwd, vault);
201
+ if (!fs.existsSync(vaultDir)) {
202
+ fs.mkdirSync(vaultDir, { recursive: true });
203
+ console.log(` ✓ ${vault}/ (created)`);
204
+ } else {
205
+ console.log(` · ${vault}/ (already exists)`);
206
+ }
207
+
208
+ // .mcp.json
209
+ const mcpJsonPath = path.join(cwd, ".mcp.json");
210
+ const mcpJson = readJsonFile(mcpJsonPath) || {};
211
+ deepMerge(mcpJson, {
212
+ mcpServers: {
213
+ "fathom-vault": {
214
+ command: "npx",
215
+ args: ["-y", "fathom-mcp"],
216
+ },
217
+ },
218
+ });
219
+ writeJsonFile(mcpJsonPath, mcpJson);
220
+ console.log(" ✓ .mcp.json");
221
+
222
+ // .claude/settings.local.json — hook registrations
223
+ const claudeSettingsPath = path.join(cwd, ".claude", "settings.local.json");
224
+ const claudeSettings = readJsonFile(claudeSettingsPath) || {};
225
+
226
+ const hooks = {};
227
+ if (enableContextHook) {
228
+ hooks["UserPromptSubmit"] = [
229
+ ...(claudeSettings.hooks?.["UserPromptSubmit"] || []),
230
+ ];
231
+ // Avoid duplicate
232
+ const contextCmd = "bash .fathom/scripts/fathom-context.sh";
233
+ if (!hooks["UserPromptSubmit"].some((h) => h.command === contextCmd)) {
234
+ hooks["UserPromptSubmit"].push({
235
+ type: "command",
236
+ command: contextCmd,
237
+ });
238
+ }
239
+ }
240
+ if (enablePrecompactHook) {
241
+ hooks["PreCompact"] = [
242
+ ...(claudeSettings.hooks?.["PreCompact"] || []),
243
+ ];
244
+ const precompactCmd = "bash .fathom/scripts/fathom-precompact.sh";
245
+ if (!hooks["PreCompact"].some((h) => h.command === precompactCmd)) {
246
+ hooks["PreCompact"].push({
247
+ type: "command",
248
+ command: precompactCmd,
249
+ });
250
+ }
251
+ }
252
+
253
+ if (Object.keys(hooks).length > 0) {
254
+ claudeSettings.hooks = { ...(claudeSettings.hooks || {}), ...hooks };
255
+ writeJsonFile(claudeSettingsPath, claudeSettings);
256
+ console.log(" ✓ .claude/settings.local.json (hooks)");
257
+ }
258
+
259
+ // .gitignore
260
+ appendToGitignore(cwd, [".fathom.json", ".fathom/scripts/"]);
261
+ console.log(" ✓ .gitignore");
262
+
263
+ // Register with server (best-effort)
264
+ if (serverUrl) {
265
+ const regClient = createClient({ server: serverUrl, apiKey, workspace });
266
+ const isUp = await regClient.healthCheck();
267
+ if (isUp) {
268
+ const regResult = await regClient.registerWorkspace(workspace, cwd);
269
+ if (regResult.ok) {
270
+ console.log(` ✓ Registered workspace "${workspace}" with server`);
271
+ } else if (regResult.error) {
272
+ console.log(` · Server: ${regResult.error}`);
273
+ }
274
+ }
275
+ }
276
+
277
+ console.log(`
278
+ Done! Fathom MCP is configured for workspace "${workspace}".
279
+
280
+ Next steps:
281
+ 1. Start the server: cd fathom-server && python app.py
282
+ 2. Open Claude Code in this directory — fathom tools will be available.
283
+ 3. Add the following to your CLAUDE.md:
284
+
285
+ ## Fathom Vault
286
+ Load fathom tools on startup: \`ToolSearch query="+fathom" max_results=20\`
287
+ `);
288
+ }
289
+
290
+ // --- Status command ----------------------------------------------------------
291
+
292
+ async function runStatus() {
293
+ const config = resolveConfig();
294
+ const client = createClient(config);
295
+
296
+ console.log("\n Fathom MCP Status\n");
297
+ console.log(` Config: ${config._configPath || "(not found — using defaults)"}`);
298
+ console.log(` Workspace: ${config.workspace}`);
299
+ console.log(` Vault: ${config.vault}`);
300
+ console.log(` Server: ${config.server}`);
301
+ console.log(` API Key: ${config.apiKey ? config.apiKey.slice(0, 7) + "..." + config.apiKey.slice(-4) : "(not set)"}`);
302
+
303
+ // Check vault directory
304
+ const vaultExists = fs.existsSync(config.vault);
305
+ console.log(`\n Vault dir: ${vaultExists ? "✓ exists" : "✗ not found"}`);
306
+
307
+ // Check server
308
+ const isUp = await client.healthCheck();
309
+ console.log(` Server: ${isUp ? "✓ reachable" : "✗ not reachable"}`);
310
+
311
+ if (isUp) {
312
+ const wsResult = await client.listWorkspaces();
313
+ if (wsResult.profiles) {
314
+ const names = Object.keys(wsResult.profiles);
315
+ console.log(` Workspaces: ${names.join(", ") || "(none)"}`);
316
+ for (const [name, profile] of Object.entries(wsResult.profiles)) {
317
+ const status = profile.running ? "running" : "stopped";
318
+ console.log(` ${name}: ${status}${profile.model ? ` (${profile.model})` : ""}`);
319
+ }
320
+ }
321
+ }
322
+
323
+ console.log();
324
+ }
325
+
326
+ // --- Main --------------------------------------------------------------------
327
+
328
+ const command = process.argv[2];
329
+
330
+ if (command === "init") {
331
+ runInit().catch((e) => {
332
+ console.error(`Error: ${e.message}`);
333
+ process.exit(1);
334
+ });
335
+ } else if (command === "status") {
336
+ runStatus().catch((e) => {
337
+ console.error(`Error: ${e.message}`);
338
+ process.exit(1);
339
+ });
340
+ } else if (!command || command === "serve") {
341
+ // Default: start MCP server
342
+ import("./index.js");
343
+ } else {
344
+ console.error(`Unknown command: ${command}`);
345
+ console.error("Usage: fathom-mcp [init|status|serve]");
346
+ process.exit(1);
347
+ }