fathom-mcp 0.4.12 → 0.4.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fathom-mcp",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -227,9 +227,8 @@ const tools = [
227
227
  "to the latest message. Default: 60 minutes before the latest message. Use start to look " +
228
228
  "further back. Example: minutes=15, start=120 returns 15 minutes of conversation starting " +
229
229
  "2 hours before the latest message. Response includes window metadata with has_older flag " +
230
- "for pseudo-pagination. For virtual rooms (dm:*, mentions:*), messages are consumed on read " +
231
- "deleted after being returned. Use mark_read=false to peek without consuming. " +
232
- "For regular rooms, automatically marks as read unless mark_read=false.",
230
+ "for pseudo-pagination. All rooms are persistent messages are never deleted on read. " +
231
+ "Automatically marks the room as read unless mark_read=false.",
233
232
  inputSchema: {
234
233
  type: "object",
235
234
  properties: {
@@ -246,8 +245,8 @@ const tools = [
246
245
  description:
247
246
  "List all rooms with activity summary — message count, last activity time, last sender, " +
248
247
  "description, and per-room unread_count for this workspace. Use to discover active rooms " +
249
- "and see which have new messages. Virtual rooms (dm:{workspace}, mentions:{workspace}) " +
250
- "appear only for the owning workspace and disappear when empty.",
248
+ "and see which have new messages. DM rooms (dm:a+b) are filtered by workspace param — " +
249
+ "only participants see them. Mention rooms (mentions:{workspace}) visible only to the target workspace.",
251
250
  inputSchema: {
252
251
  type: "object",
253
252
  properties: {},
@@ -282,10 +281,9 @@ const tools = [
282
281
  {
283
282
  name: "fathom_send",
284
283
  description:
285
- "Send a message to another workspace's agent instance — stored in their dm:{workspace} " +
286
- "virtual room. Use fathom_workspaces first to discover valid targets. Recipients see DMs " +
287
- "via fathom_room_list and consume them with fathom_room_read. " +
288
- "Message format: 'Message from workspace ({from}): {message}'",
284
+ "Send a message to another workspace's agent instance — stored in a shared dm:a+b room " +
285
+ "visible to both participants. Use fathom_workspaces first to discover valid targets. " +
286
+ "DMs are persistent and appear in both participants' room lists via fathom_room_list.",
289
287
  inputSchema: {
290
288
  type: "object",
291
289
  properties: {
@@ -344,6 +342,99 @@ const tools = [
344
342
  required: [],
345
343
  },
346
344
  },
345
+ {
346
+ name: "fathom_routine_list",
347
+ description:
348
+ "List all ping routines for a workspace with their status, intervals, " +
349
+ "enabled state, and next fire time. Use this to see what routines exist " +
350
+ "before updating or deleting them.",
351
+ inputSchema: {
352
+ type: "object",
353
+ properties: {
354
+ workspace: WORKSPACE_PROP,
355
+ },
356
+ required: [],
357
+ },
358
+ },
359
+ {
360
+ name: "fathom_routine_update",
361
+ description:
362
+ "Update an existing ping routine. Only provided fields are changed — " +
363
+ "omitted fields keep their current values. Use fathom_routine_list first " +
364
+ "to find the routine_id.",
365
+ inputSchema: {
366
+ type: "object",
367
+ properties: {
368
+ routine_id: { type: "string", description: "The routine ID to update. Use fathom_routine_list to find IDs." },
369
+ name: { type: "string", description: "New routine name." },
370
+ enabled: { type: "boolean", description: "Enable or disable the routine." },
371
+ interval_minutes: { type: "integer", description: "New interval in minutes.", minimum: 1 },
372
+ single_fire: { type: "boolean", description: "Auto-disable after firing once." },
373
+ workspace: WORKSPACE_PROP,
374
+ context_sources: {
375
+ type: "object",
376
+ description: "What to inject on each ping.",
377
+ properties: {
378
+ time: { type: "boolean", description: "Include current time/date. Default: true." },
379
+ scripts: {
380
+ type: "array",
381
+ description: "Shell commands to run and inject output.",
382
+ items: {
383
+ type: "object",
384
+ properties: {
385
+ label: { type: "string" },
386
+ command: { type: "string" },
387
+ enabled: { type: "boolean" },
388
+ },
389
+ },
390
+ },
391
+ texts: {
392
+ type: "array",
393
+ description: "Static text blocks to inject.",
394
+ items: {
395
+ type: "object",
396
+ properties: {
397
+ label: { type: "string" },
398
+ content: { type: "string" },
399
+ enabled: { type: "boolean" },
400
+ },
401
+ },
402
+ },
403
+ },
404
+ },
405
+ },
406
+ required: ["routine_id"],
407
+ },
408
+ },
409
+ {
410
+ name: "fathom_routine_delete",
411
+ description:
412
+ "Delete a ping routine permanently. Use fathom_routine_list first to find " +
413
+ "the routine_id. This cannot be undone — create a new routine if needed.",
414
+ inputSchema: {
415
+ type: "object",
416
+ properties: {
417
+ routine_id: { type: "string", description: "The routine ID to delete." },
418
+ workspace: WORKSPACE_PROP,
419
+ },
420
+ required: ["routine_id"],
421
+ },
422
+ },
423
+ {
424
+ name: "fathom_routine_fire",
425
+ description:
426
+ "Fire a ping routine immediately, regardless of its schedule. Non-blocking — " +
427
+ "returns immediately while the routine fires in the background. The routine's " +
428
+ "next scheduled fire time is not affected.",
429
+ inputSchema: {
430
+ type: "object",
431
+ properties: {
432
+ routine_id: { type: "string", description: "The routine ID to fire." },
433
+ workspace: WORKSPACE_PROP,
434
+ },
435
+ required: ["routine_id"],
436
+ },
437
+ },
347
438
  ];
348
439
 
349
440
  // --- Vault routing by mode ---------------------------------------------------
@@ -381,6 +472,47 @@ function resolveVault(args) {
381
472
  }
382
473
  }
383
474
 
475
+ // --- Telegram tools (primary agent only) -------------------------------------
476
+
477
+ const telegramTools = [
478
+ {
479
+ name: "fathom_telegram_contacts",
480
+ description:
481
+ "List Telegram contacts who have messaged. Returns name, username, chat_id, " +
482
+ "last_message time, and unread count.",
483
+ inputSchema: { type: "object", properties: {} },
484
+ },
485
+ {
486
+ name: "fathom_telegram_read",
487
+ description:
488
+ "Read recent Telegram messages from a contact. Same windowing as fathom_room_read — " +
489
+ "anchored to latest message, default 60 minutes. Use start to look further back.",
490
+ inputSchema: {
491
+ type: "object",
492
+ properties: {
493
+ contact: { type: "string", description: "Contact name, @username, or chat_id" },
494
+ minutes: { type: "number", description: "Window duration in minutes. Default: 60." },
495
+ start: { type: "number", description: "Offset in minutes from latest message. Default: 0." },
496
+ mark_read: { type: "boolean", description: "Mark messages as read. Default: true." },
497
+ },
498
+ required: ["contact"],
499
+ },
500
+ },
501
+ {
502
+ name: "fathom_telegram_send",
503
+ description:
504
+ "Send a Telegram message to a contact via the persistent Telethon client.",
505
+ inputSchema: {
506
+ type: "object",
507
+ properties: {
508
+ contact: { type: "string", description: "Contact name, @username, or chat_id" },
509
+ message: { type: "string", description: "Message text to send" },
510
+ },
511
+ required: ["contact", "message"],
512
+ },
513
+ },
514
+ ];
515
+
384
516
  // --- Server setup & dispatch -------------------------------------------------
385
517
 
386
518
  const server = new Server(
@@ -388,7 +520,19 @@ const server = new Server(
388
520
  { capabilities: { tools: {} } },
389
521
  );
390
522
 
391
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
523
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
524
+ // Telegram tools only visible to the primary (default) workspace
525
+ let isPrimary = false;
526
+ try {
527
+ const settings = await client.getSettings();
528
+ const primaryAgent = settings.default_workspace;
529
+ isPrimary = config.workspace === primaryAgent;
530
+ } catch {
531
+ // If settings unavailable, hide telegram tools
532
+ }
533
+ const allTools = [...tools, ...(isPrimary ? telegramTools : [])];
534
+ return { tools: allTools };
535
+ });
392
536
 
393
537
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
394
538
  const { name, arguments: args } = request.params;
@@ -543,6 +687,76 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
543
687
  result = await client.createRoutine(routineParams, args.workspace || config.workspace);
544
688
  break;
545
689
  }
690
+ case "fathom_routine_list":
691
+ result = await client.listRoutines(args.workspace || config.workspace);
692
+ break;
693
+ case "fathom_routine_update": {
694
+ const updateParams = {};
695
+ if (args.name != null) updateParams.name = args.name;
696
+ if (args.enabled != null) updateParams.enabled = args.enabled;
697
+ if (args.interval_minutes != null) updateParams.intervalMinutes = args.interval_minutes;
698
+ if (args.single_fire != null) updateParams.singleFire = args.single_fire;
699
+ if (args.context_sources != null) updateParams.contextSources = args.context_sources;
700
+ result = await client.updateRoutine(args.routine_id, updateParams, args.workspace || config.workspace);
701
+ break;
702
+ }
703
+ case "fathom_routine_delete":
704
+ result = await client.deleteRoutine(args.routine_id, args.workspace || config.workspace);
705
+ break;
706
+ case "fathom_routine_fire":
707
+ result = await client.fireRoutine(args.routine_id, args.workspace || config.workspace);
708
+ break;
709
+ // --- Telegram ---
710
+ case "fathom_telegram_contacts":
711
+ result = await client.telegramContacts(config.workspace);
712
+ break;
713
+ case "fathom_telegram_read": {
714
+ // Resolve contact name to chat_id via contacts list
715
+ const contacts = await client.telegramContacts(config.workspace);
716
+ const contactList = contacts?.contacts || [];
717
+ const contactArg = (args.contact || "").trim();
718
+ let chatId = parseInt(contactArg, 10);
719
+ if (isNaN(chatId)) {
720
+ const lower = contactArg.toLowerCase().replace(/^@/, "");
721
+ const match = contactList.find(c =>
722
+ (c.username || "").toLowerCase() === lower ||
723
+ (c.first_name || "").toLowerCase() === lower ||
724
+ (c.first_name || "").toLowerCase().includes(lower)
725
+ );
726
+ chatId = match ? match.chat_id : null;
727
+ }
728
+ if (!chatId) {
729
+ result = { error: `Contact not found: ${contactArg}. Use fathom_telegram_contacts to list known contacts.` };
730
+ } else {
731
+ result = await client.telegramRead(
732
+ chatId, args.minutes, args.start,
733
+ args.mark_read !== false ? config.workspace : undefined,
734
+ args.mark_read,
735
+ );
736
+ }
737
+ break;
738
+ }
739
+ case "fathom_telegram_send": {
740
+ const sendContacts = await client.telegramContacts(config.workspace);
741
+ const sendList = sendContacts?.contacts || [];
742
+ const sendArg = (args.contact || "").trim();
743
+ let sendChatId = parseInt(sendArg, 10);
744
+ if (isNaN(sendChatId)) {
745
+ const lower = sendArg.toLowerCase().replace(/^@/, "");
746
+ const match = sendList.find(c =>
747
+ (c.username || "").toLowerCase() === lower ||
748
+ (c.first_name || "").toLowerCase() === lower ||
749
+ (c.first_name || "").toLowerCase().includes(lower)
750
+ );
751
+ sendChatId = match ? match.chat_id : null;
752
+ }
753
+ if (!sendChatId) {
754
+ result = { error: `Contact not found: ${sendArg}. Use fathom_telegram_contacts to list known contacts.` };
755
+ } else {
756
+ result = await client.telegramSend(sendChatId, args.message);
757
+ }
758
+ break;
759
+ }
546
760
  default:
547
761
  result = { error: `Unknown tool: ${name}` };
548
762
  }
@@ -205,6 +205,37 @@ export function createClient(config) {
205
205
  });
206
206
  }
207
207
 
208
+ async function listRoutines(ws) {
209
+ return request("GET", "/api/activation/ping/routines", {
210
+ params: { workspace: ws },
211
+ });
212
+ }
213
+
214
+ async function updateRoutine(routineId, params, ws) {
215
+ const body = {};
216
+ if (params.name != null) body.name = params.name;
217
+ if (params.enabled != null) body.enabled = params.enabled;
218
+ if (params.intervalMinutes != null) body.intervalMinutes = params.intervalMinutes;
219
+ if (params.singleFire != null) body.singleFire = params.singleFire;
220
+ if (params.contextSources != null) body.contextSources = params.contextSources;
221
+ return request("POST", `/api/activation/ping/routines/${encodeURIComponent(routineId)}`, {
222
+ params: { workspace: ws },
223
+ body,
224
+ });
225
+ }
226
+
227
+ async function deleteRoutine(routineId, ws) {
228
+ return request("DELETE", `/api/activation/ping/routines/${encodeURIComponent(routineId)}`, {
229
+ params: { workspace: ws },
230
+ });
231
+ }
232
+
233
+ async function fireRoutine(routineId, ws) {
234
+ return request("POST", `/api/activation/ping/routines/${encodeURIComponent(routineId)}/now`, {
235
+ params: { workspace: ws },
236
+ });
237
+ }
238
+
208
239
  // --- Heartbeat -------------------------------------------------------------
209
240
 
210
241
  async function heartbeat(ws, agent, vaultMode) {
@@ -214,6 +245,36 @@ export function createClient(config) {
214
245
  return request("POST", `/api/workspaces/${encodeURIComponent(ws)}/heartbeat`, { body });
215
246
  }
216
247
 
248
+ // --- Telegram --------------------------------------------------------------
249
+
250
+ async function telegramContacts(ws) {
251
+ return request("GET", "/api/telegram/contacts", {
252
+ params: { workspace: ws },
253
+ });
254
+ }
255
+
256
+ async function telegramRead(chatId, minutes, start, ws, markRead) {
257
+ return request("GET", `/api/telegram/messages/${chatId}`, {
258
+ params: { minutes, start, workspace: ws, mark_read: markRead },
259
+ });
260
+ }
261
+
262
+ async function telegramSend(chatId, message) {
263
+ return request("POST", `/api/telegram/send/${chatId}`, {
264
+ body: { message },
265
+ });
266
+ }
267
+
268
+ async function telegramStatus() {
269
+ return request("GET", "/api/telegram/status");
270
+ }
271
+
272
+ // --- Settings --------------------------------------------------------------
273
+
274
+ async function getSettings() {
275
+ return request("GET", "/api/settings");
276
+ }
277
+
217
278
  // --- Auth ------------------------------------------------------------------
218
279
 
219
280
  async function getApiKey() {
@@ -254,7 +315,16 @@ export function createClient(config) {
254
315
  pushFile,
255
316
  syncManifest,
256
317
  createRoutine,
318
+ listRoutines,
319
+ updateRoutine,
320
+ deleteRoutine,
321
+ fireRoutine,
257
322
  heartbeat,
323
+ telegramContacts,
324
+ telegramRead,
325
+ telegramSend,
326
+ telegramStatus,
327
+ getSettings,
258
328
  getApiKey,
259
329
  healthCheck,
260
330
  };