fathom-mcp 0.4.2 → 0.4.4

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/README.md CHANGED
@@ -70,7 +70,7 @@ npx fathom-mcp status # Check server connection + workspace status
70
70
  | `fathom_vault_vsearch` | Semantic/vector search |
71
71
  | `fathom_vault_query` | Hybrid search (BM25 + vectors + reranking) |
72
72
  | `fathom_room_post` | Post to a shared room (supports @mentions) |
73
- | `fathom_room_read` | Read recent room messages |
73
+ | `fathom_room_read` | Read room messages (windowed, anchored to latest; `minutes`/`start` for pagination) |
74
74
  | `fathom_room_list` | List all rooms |
75
75
  | `fathom_room_describe` | Set a room's description/topic |
76
76
  | `fathom_workspaces` | List all configured workspaces |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fathom-mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,6 +6,9 @@
6
6
 
7
7
  set -euo pipefail
8
8
 
9
+ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ TOAST="$HOOK_DIR/hook-toast.sh"
11
+
9
12
  # Walk up to find .fathom.json
10
13
  find_config() {
11
14
  local dir="$PWD"
@@ -30,18 +33,29 @@ if [ "$HOOK_ENABLED" != "True" ] && [ "$HOOK_ENABLED" != "true" ]; then
30
33
  exit 0
31
34
  fi
32
35
 
36
+ # Toast: start multi-stage
37
+ "$TOAST" memento --status precompact &>/dev/null
38
+ "$TOAST" --update precompact "⏳ Getting context..." &>/dev/null
39
+
33
40
  # Read PreCompact input (contains transcript_path)
34
41
  INPUT=$(cat)
35
42
  TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null || echo "")
36
43
 
37
44
  if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
45
+ "$TOAST" --update precompact "✗ No transcript found" &>/dev/null
46
+ "$TOAST" --close precompact &>/dev/null &
38
47
  exit 0
39
48
  fi
40
49
 
50
+ # Toast: extracting
51
+ "$TOAST" --update precompact "⏳ Extracting memories..." &>/dev/null
52
+
41
53
  # Extract vault file paths from transcript
42
54
  VAULT_FILES=$(grep -oP 'vault/[a-zA-Z0-9_/.-]+\.md' "$TRANSCRIPT_PATH" 2>/dev/null | sort -u || echo "")
43
55
 
44
56
  if [ -z "$VAULT_FILES" ]; then
57
+ "$TOAST" --update precompact "✓ No vault files to record" &>/dev/null
58
+ ("$TOAST" --close precompact &>/dev/null &)
45
59
  exit 0
46
60
  fi
47
61
 
@@ -59,6 +73,11 @@ done
59
73
 
60
74
  # Output summary
61
75
  FILE_COUNT=$(echo "$VAULT_FILES" | wc -l)
76
+
77
+ # Toast: done
78
+ "$TOAST" --update precompact "✓ Stored ${FILE_COUNT} vault file(s)" &>/dev/null
79
+ ("$TOAST" --close precompact &>/dev/null &)
80
+
62
81
  python3 -c "
63
82
  import json, sys
64
83
  result = {
@@ -11,6 +11,7 @@
11
11
  set -o pipefail
12
12
 
13
13
  HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
14
+ TOAST="$HOOK_DIR/hook-toast.sh"
14
15
  VSEARCH_CACHE="/tmp/fathom-vsearch-cache.json"
15
16
  VSEARCH_LOCK="/tmp/fathom-vsearch.lock"
16
17
  STALE_LOCK_SECONDS=180 # 3 minutes — lock older than this is considered stale
@@ -75,6 +76,9 @@ fi
75
76
 
76
77
  QUERY="${USER_MESSAGE:0:500}"
77
78
 
79
+ # Toast: start retrieving
80
+ "$TOAST" fathom "⏳ Retrieving docs..." &>/dev/null
81
+
78
82
  # --- Phase 1: Read cached vsearch results from previous query ---
79
83
  CACHED_VSEARCH=""
80
84
  if [ -f "$VSEARCH_CACHE" ]; then
@@ -151,6 +155,10 @@ if [ -n "$BM25_RESULTS" ] || [ -n "$CACHED_VSEARCH" ]; then
151
155
  fi
152
156
 
153
157
  SUMMARY="Fathom Vault: ${VAULT_COUNT} memories"
158
+
159
+ # Toast: result
160
+ "$TOAST" fathom "✓ ${VAULT_COUNT} docs recalled" &>/dev/null
161
+
154
162
  python3 -c "
155
163
  import json, sys
156
164
  summary = sys.argv[1]
@@ -163,6 +171,9 @@ print(json.dumps({
163
171
  }
164
172
  }))
165
173
  " "$SUMMARY" "$DETAIL_TEXT"
174
+ else
175
+ # Toast: no results
176
+ "$TOAST" fathom "✓ No docs matched" &>/dev/null
166
177
  fi
167
178
 
168
179
  # --- Phase 3: Launch background vsearch for current query ---
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ # hook-toast.sh — tmux popup notifications for agent hooks.
3
+ #
4
+ # Usage:
5
+ # hook-toast.sh <system> <message> # one-shot toast (auto-closes after 2s)
6
+ # hook-toast.sh <system> --status <id> # start a multi-stage toast (poll mode)
7
+ # hook-toast.sh --update <id> <message> # update a running toast
8
+ # hook-toast.sh --close <id> # close a running toast (after delay)
9
+ #
10
+ # Built-in systems: fathom, memento
11
+ # fathom → 📝 purple (colour141)
12
+ # memento → 🧠 teal (colour37)
13
+ #
14
+ # Any other system name works too — gets a default icon and grey border.
15
+ #
16
+ # Multi-stage example (PreCompact):
17
+ # hook-toast.sh memento --status precompact
18
+ # hook-toast.sh --update precompact "⏳ Getting context..."
19
+ # hook-toast.sh --update precompact "⏳ Extracting memories..."
20
+ # hook-toast.sh --update precompact "✓ Stored 7 memories"
21
+ # hook-toast.sh --close precompact
22
+
23
+ TOAST_DIR="/tmp/hook-toast"
24
+ mkdir -p "$TOAST_DIR"
25
+
26
+ # Bail silently if not in tmux
27
+ if ! tmux info &>/dev/null; then
28
+ exit 0
29
+ fi
30
+
31
+ get_style() {
32
+ case "$1" in
33
+ memento) echo "colour37" ;;
34
+ fathom) echo "colour141" ;;
35
+ *) echo "colour245" ;;
36
+ esac
37
+ }
38
+
39
+ get_icon() {
40
+ case "$1" in
41
+ memento) echo "🧠" ;;
42
+ fathom) echo "📝" ;;
43
+ *) echo "📌" ;;
44
+ esac
45
+ }
46
+
47
+ get_title() {
48
+ case "$1" in
49
+ memento) echo "Memento" ;;
50
+ fathom) echo "Fathom" ;;
51
+ *) echo "$1" ;;
52
+ esac
53
+ }
54
+
55
+ # One-shot toast
56
+ toast_oneshot() {
57
+ local system="$1"
58
+ local message="$2"
59
+ local colour icon title
60
+ colour=$(get_style "$system")
61
+ icon=$(get_icon "$system")
62
+ title=$(get_title "$system")
63
+
64
+ (tmux display-popup -x R -y 0 -w 42 -h 3 \
65
+ -s "fg=$colour" -T " $icon $title " -E \
66
+ "echo ' $message'; sleep 2" &>/dev/null &)
67
+ }
68
+
69
+ # Start a multi-stage toast (polling status file)
70
+ toast_start() {
71
+ local system="$1"
72
+ local id="$2"
73
+ local status_file="$TOAST_DIR/$id"
74
+ local colour icon title
75
+ colour=$(get_style "$system")
76
+ icon=$(get_icon "$system")
77
+ title=$(get_title "$system")
78
+
79
+ echo "⏳ Starting..." > "$status_file"
80
+
81
+ (tmux display-popup -x R -y 0 -w 42 -h 3 \
82
+ -s "fg=$colour" -T " $icon $title " -E "
83
+ LAST=''
84
+ while [ -f '$status_file' ]; do
85
+ MSG=\$(cat '$status_file' 2>/dev/null)
86
+ if [ \"\$MSG\" != \"\$LAST\" ]; then
87
+ clear
88
+ echo \" \$MSG\"
89
+ LAST=\"\$MSG\"
90
+ fi
91
+ case \"\$MSG\" in ✓*|✗*) sleep 2; break ;; esac
92
+ sleep 0.2
93
+ done
94
+ " &>/dev/null &)
95
+ }
96
+
97
+ # Update a running toast
98
+ toast_update() {
99
+ local id="$1"
100
+ local message="$2"
101
+ echo "$message" > "$TOAST_DIR/$id"
102
+ }
103
+
104
+ # Close a running toast (remove status file — popup exits on next poll)
105
+ toast_close() {
106
+ local id="$1"
107
+ # Give the popup time to display the final message
108
+ sleep 2
109
+ rm -f "$TOAST_DIR/$id"
110
+ }
111
+
112
+ # --- Argument parsing ---
113
+ case "${1:-}" in
114
+ --update)
115
+ toast_update "$2" "$3"
116
+ ;;
117
+ --close)
118
+ toast_close "$2"
119
+ ;;
120
+ --*)
121
+ echo "Unknown option: $1" >&2
122
+ exit 1
123
+ ;;
124
+ "")
125
+ echo "Usage: hook-toast.sh <system> <message>" >&2
126
+ echo " hook-toast.sh <system> --status <id>" >&2
127
+ echo " hook-toast.sh --update <id> <message>" >&2
128
+ echo " hook-toast.sh --close <id>" >&2
129
+ exit 1
130
+ ;;
131
+ *)
132
+ SYSTEM="$1"
133
+ shift
134
+ if [ "${1:-}" = "--status" ]; then
135
+ toast_start "$SYSTEM" "$2"
136
+ else
137
+ toast_oneshot "$SYSTEM" "$*"
138
+ fi
139
+ ;;
140
+ esac
package/src/index.js CHANGED
@@ -220,13 +220,17 @@ const tools = [
220
220
  {
221
221
  name: "fathom_room_read",
222
222
  description:
223
- "Read recent messages from a shared room. Returns messages from the last N hours " +
224
- "(default 24). Use during orient phase to catch up on cross-workspace conversation.",
223
+ "Read recent messages from a shared room. Returns messages within a time window anchored " +
224
+ "to the latest message. Default: 60 minutes before the latest message. Use start to look " +
225
+ "further back. Example: minutes=15, start=120 returns 15 minutes of conversation starting " +
226
+ "2 hours before the latest message. Response includes window metadata with has_older flag " +
227
+ "for pseudo-pagination.",
225
228
  inputSchema: {
226
229
  type: "object",
227
230
  properties: {
228
231
  room: { type: "string", description: "Room name to read from" },
229
- hours: { type: "number", description: "How many hours of history to return. Default: 24." },
232
+ minutes: { type: "number", description: "Window duration in minutes. Default: 60." },
233
+ start: { type: "number", description: "Offset in minutes from the latest message. Default: 0 (window ends at latest message). Set to 120 to look back starting 2 hours before the latest message." },
230
234
  },
231
235
  required: ["room"],
232
236
  },
@@ -412,7 +416,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
412
416
  result = await client.roomPost(args.room, args.message, config.workspace);
413
417
  break;
414
418
  case "fathom_room_read":
415
- result = await client.roomRead(args.room, args.hours);
419
+ result = await client.roomRead(args.room, args.minutes, args.start);
416
420
  break;
417
421
  case "fathom_room_list":
418
422
  result = await client.roomList();
@@ -82,9 +82,9 @@ export function createClient(config) {
82
82
  });
83
83
  }
84
84
 
85
- async function roomRead(room, hours) {
85
+ async function roomRead(room, minutes, start) {
86
86
  return request("GET", `/api/room/${encodeURIComponent(room)}`, {
87
- params: { hours },
87
+ params: { minutes, start },
88
88
  });
89
89
  }
90
90