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 +1 -1
- package/src/cli.js +20 -5
- package/src/index.js +77 -0
- package/src/server-client.js +26 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -411,19 +411,20 @@ async function runInit(flags = {}) {
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
// 5. Server URL
|
|
414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
423
|
-
|
|
422
|
+
let regClient = createClient({ server: serverUrl, apiKey, workspace });
|
|
423
|
+
let serverReachable = serverUrl ? await regClient.healthCheck() : false;
|
|
424
424
|
const serverOnPath = detectFathomServer();
|
|
425
425
|
|
|
426
|
-
|
|
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
|
}
|
package/src/server-client.js
CHANGED
|
@@ -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
|
};
|