fathom-mcp 0.4.9 → 0.4.11

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.9",
3
+ "version": "0.4.11",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -411,19 +411,20 @@ async function runInit(flags = {}) {
411
411
  }
412
412
 
413
413
  // 5. Server URL
414
- const serverUrl = nonInteractive
414
+ let serverUrl = nonInteractive
415
415
  ? (flagServer || "http://localhost:4243")
416
416
  : await ask(rl, "\n Fathom server URL", "http://localhost:4243");
417
417
 
418
418
  // 6. API key
419
- const apiKey = flagApiKey || (nonInteractive ? "" : await ask(rl, " API key (from dashboard or server first-run output)", ""));
419
+ let apiKey = flagApiKey || (nonInteractive ? "" : await ask(rl, " API key (from dashboard or server first-run output)", ""));
420
420
 
421
421
  // 7. Server probe — check reachability early
422
- const regClient = createClient({ server: serverUrl, apiKey, workspace });
423
- const serverReachable = serverUrl ? await regClient.healthCheck() : false;
422
+ let regClient = createClient({ server: serverUrl, apiKey, workspace });
423
+ let serverReachable = serverUrl ? await regClient.healthCheck() : false;
424
424
  const serverOnPath = detectFathomServer();
425
425
 
426
- if (!serverReachable) {
426
+ // Retry loop for server connectivity (interactive mode)
427
+ while (!serverReachable && !nonInteractive) {
427
428
  console.log(`\n ⚠ Fathom server not reachable at ${serverUrl}\n`);
428
429
  if (serverOnPath === "installed") {
429
430
  console.log(" Start it: fathom-server");
@@ -433,11 +434,25 @@ async function runInit(flags = {}) {
433
434
  console.log(" # or: docker run -p 4243:4243 ghcr.io/myra/fathom-server");
434
435
  }
435
436
  console.log("\n Without the server, only \"local\" and \"none\" vault modes are available.");
437
+ const retry = await askYesNo(rl, "\n Try a different server URL or API key?", true);
438
+ if (!retry) break;
439
+ serverUrl = await ask(rl, "\n Fathom server URL", serverUrl);
440
+ apiKey = await ask(rl, " API key", apiKey);
441
+ regClient = createClient({ server: serverUrl, apiKey, workspace });
442
+ serverReachable = await regClient.healthCheck();
443
+ if (serverReachable) {
444
+ console.log(" ✓ Server connected!");
445
+ }
436
446
  }
437
447
 
438
448
  // 8. Vault mode selection
439
449
  let vaultMode;
440
450
  if (nonInteractive) {
451
+ if (!serverReachable && flagServer) {
452
+ console.error(`\n Error: Server at ${serverUrl} is not reachable.`);
453
+ console.error(" Fix the URL or start the server, then re-run init.");
454
+ process.exit(1);
455
+ }
441
456
  vaultMode = serverReachable ? "hosted" : "local";
442
457
  console.log(` Vault mode: ${vaultMode} (auto-selected)`);
443
458
  } else {
package/src/index.js CHANGED
@@ -291,6 +291,55 @@ const tools = [
291
291
  required: ["workspace", "message"],
292
292
  },
293
293
  },
294
+ {
295
+ name: "fathom_routine_create",
296
+ description:
297
+ "Create a new ping routine for a workspace. Routines fire on an interval and inject " +
298
+ "context into the persistent session. Use single_fire for one-shot routines that " +
299
+ "auto-disable after firing once. All parameters are optional with sensible defaults.",
300
+ inputSchema: {
301
+ type: "object",
302
+ properties: {
303
+ name: { type: "string", description: "Routine name. Default: 'New Routine'." },
304
+ enabled: { type: "boolean", description: "Start enabled. Default: false." },
305
+ interval_minutes: { type: "integer", description: "Minutes between pings. Default: 60.", minimum: 1 },
306
+ single_fire: { type: "boolean", description: "Auto-disable after firing once. Default: false." },
307
+ workspace: WORKSPACE_PROP,
308
+ context_sources: {
309
+ type: "object",
310
+ description: "What to inject on each ping.",
311
+ properties: {
312
+ time: { type: "boolean", description: "Include current time/date. Default: true." },
313
+ scripts: {
314
+ type: "array",
315
+ description: "Shell commands to run and inject output.",
316
+ items: {
317
+ type: "object",
318
+ properties: {
319
+ label: { type: "string" },
320
+ command: { type: "string" },
321
+ enabled: { type: "boolean" },
322
+ },
323
+ },
324
+ },
325
+ texts: {
326
+ type: "array",
327
+ description: "Static text blocks to inject.",
328
+ items: {
329
+ type: "object",
330
+ properties: {
331
+ label: { type: "string" },
332
+ content: { type: "string" },
333
+ enabled: { type: "boolean" },
334
+ },
335
+ },
336
+ },
337
+ },
338
+ },
339
+ },
340
+ required: [],
341
+ },
342
+ },
294
343
  ];
295
344
 
296
345
  // --- Vault routing by mode ---------------------------------------------------
@@ -480,6 +529,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
480
529
  case "fathom_send":
481
530
  result = await client.sendToWorkspace(args.workspace, args.message, config.workspace);
482
531
  break;
532
+ case "fathom_routine_create": {
533
+ const routineParams = {};
534
+ if (args.name != null) routineParams.name = args.name;
535
+ if (args.enabled != null) routineParams.enabled = args.enabled;
536
+ if (args.interval_minutes != null) routineParams.intervalMinutes = args.interval_minutes;
537
+ if (args.single_fire != null) routineParams.singleFire = args.single_fire;
538
+ if (args.context_sources != null) routineParams.contextSources = args.context_sources;
539
+ result = await client.createRoutine(routineParams, args.workspace || config.workspace);
540
+ break;
541
+ }
483
542
  default:
484
543
  result = { error: `Unknown tool: ${name}` };
485
544
  }
@@ -539,6 +598,13 @@ async function startupSync() {
539
598
  await client.pushFile(config.workspace, filePath, content);
540
599
  }
541
600
  }
601
+
602
+ // Delete server files that no longer exist locally (local is source of truth)
603
+ if (diff.deleted?.length) {
604
+ for (const filePath of diff.deleted) {
605
+ await client.deleteFile(config.workspace, filePath);
606
+ }
607
+ }
542
608
  } catch {
543
609
  // Sync failure is non-fatal — local vault is source of truth
544
610
  }
@@ -551,12 +617,23 @@ async function main() {
551
617
  vault: config._rawVault,
552
618
  description: config.description,
553
619
  agents: config.agents,
620
+ type: config.vaultMode,
554
621
  }).catch(() => {});
555
622
  }
556
623
 
557
624
  // Startup sync for synced mode (fire-and-forget)
558
625
  startupSync().catch(() => {});
559
626
 
627
+ // Heartbeat — report liveness to server every 30s
628
+ if (config.server && config.workspace) {
629
+ const beat = () =>
630
+ client
631
+ .heartbeat(config.workspace, config.agents?.[0], config.vaultMode)
632
+ .catch(() => {});
633
+ beat(); // immediate
634
+ setInterval(beat, 30_000);
635
+ }
636
+
560
637
  const transport = new StdioServerTransport();
561
638
  await server.connect(transport);
562
639
  }
@@ -190,6 +190,30 @@ export function createClient(config) {
190
190
  });
191
191
  }
192
192
 
193
+ // --- Activation / Routines -------------------------------------------------
194
+
195
+ async function createRoutine(params, ws) {
196
+ const body = {};
197
+ if (params.name != null) body.name = params.name;
198
+ if (params.enabled != null) body.enabled = params.enabled;
199
+ if (params.intervalMinutes != null) body.intervalMinutes = params.intervalMinutes;
200
+ if (params.singleFire != null) body.singleFire = params.singleFire;
201
+ if (params.contextSources != null) body.contextSources = params.contextSources;
202
+ return request("POST", "/api/activation/ping/routines", {
203
+ params: { workspace: ws },
204
+ body,
205
+ });
206
+ }
207
+
208
+ // --- Heartbeat -------------------------------------------------------------
209
+
210
+ async function heartbeat(ws, agent, vaultMode) {
211
+ const body = {};
212
+ if (agent) body.agent = agent;
213
+ if (vaultMode) body.vault_mode = vaultMode;
214
+ return request("POST", `/api/workspaces/${encodeURIComponent(ws)}/heartbeat`, { body });
215
+ }
216
+
193
217
  // --- Auth ------------------------------------------------------------------
194
218
 
195
219
  async function getApiKey() {
@@ -229,6 +253,8 @@ export function createClient(config) {
229
253
  listFiles,
230
254
  pushFile,
231
255
  syncManifest,
256
+ createRoutine,
257
+ heartbeat,
232
258
  getApiKey,
233
259
  healthCheck,
234
260
  };