@macula-io/mcp 0.4.1 → 0.5.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/README.md +94 -35
- package/dist/bin/doctor.js +1 -1
- package/dist/bin/install.js +2 -2
- package/dist/bin/install.js.map +1 -1
- package/dist/bin/status.js +2 -2
- package/dist/bin/status.js.map +1 -1
- package/dist/bin/uninstall.js +1 -1
- package/dist/index.js +31 -14
- package/dist/index.js.map +1 -1
- package/dist/macula_cli.d.ts +30 -1
- package/dist/macula_cli.js +83 -21
- package/dist/macula_cli.js.map +1 -1
- package/dist/mesh_agents.d.ts +2 -0
- package/dist/mesh_agents.js +47 -0
- package/dist/mesh_agents.js.map +1 -0
- package/dist/mesh_etiquette.js +36 -7
- package/dist/mesh_etiquette.js.map +1 -1
- package/dist/mesh_goodbye.d.ts +2 -0
- package/dist/mesh_goodbye.js +24 -0
- package/dist/mesh_goodbye.js.map +1 -0
- package/dist/mesh_hello.d.ts +2 -0
- package/dist/mesh_hello.js +73 -0
- package/dist/mesh_hello.js.map +1 -0
- package/dist/mesh_help.js +22 -7
- package/dist/mesh_help.js.map +1 -1
- package/dist/presence.d.ts +22 -0
- package/dist/presence.js +217 -0
- package/dist/presence.js.map +1 -0
- package/dist/roster.d.ts +26 -0
- package/dist/roster.js +93 -0
- package/dist/roster.js.map +1 -0
- package/package.json +3 -1
package/dist/roster.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// A local, persistent cache of "who else is on the mesh" -- built
|
|
2
|
+
// entirely from consuming agent.hello/agent.goodbye facts, the same
|
|
3
|
+
// shape as a QRY-department read model built from events elsewhere in
|
|
4
|
+
// this codebase family: not an event store, just a projection over
|
|
5
|
+
// what's been observed, disposable and rebuildable from the mesh at
|
|
6
|
+
// any time (a fresh macula-mcp process starts with an empty roster and
|
|
7
|
+
// repopulates it from whoever's still heartbeating).
|
|
8
|
+
//
|
|
9
|
+
// SQLite over an in-memory Map deliberately: a Map dies with the
|
|
10
|
+
// process, and "who's on the mesh" is more useful if a restart doesn't
|
|
11
|
+
// forget everyone seen minutes ago. SQLite over PouchDB: PouchDB's
|
|
12
|
+
// whole value is offline-first sync/replication with conflict
|
|
13
|
+
// resolution across replicas -- nothing here replicates anywhere, it's
|
|
14
|
+
// one process's own local cache, and pulling in PouchDB's dependency
|
|
15
|
+
// weight for a single-writer key-value table would be solving a
|
|
16
|
+
// problem this doesn't have.
|
|
17
|
+
//
|
|
18
|
+
// better-sqlite3 is pinned to the 12.x line, NOT latest (13.x)
|
|
19
|
+
// deliberately: 13.0.3 requires Node >=22 and segfaults outright under
|
|
20
|
+
// this project's own CI-tested Node 20 (confirmed live -- exit 139,
|
|
21
|
+
// reproduced locally under Node 20 via asdf before landing the fix,
|
|
22
|
+
// npm only warns on the engine mismatch rather than failing the
|
|
23
|
+
// install, so this is silent until something actually touches the
|
|
24
|
+
// native binding). 12.11.1 explicitly declares support for 20.x --
|
|
25
|
+
// don't bump past the 12.x line without first confirming the new
|
|
26
|
+
// version's own declared `engines` covers Node 20, not just running
|
|
27
|
+
// `npm test` on whatever Node happens to be on the developer's PATH.
|
|
28
|
+
import Database from "better-sqlite3";
|
|
29
|
+
import { mkdirSync } from "node:fs";
|
|
30
|
+
import { homedir } from "node:os";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
function dbPath() {
|
|
33
|
+
return process.env.MACULA_MCP_ROSTER_DB ?? join(homedir(), ".macula-mcp", "roster.sqlite3");
|
|
34
|
+
}
|
|
35
|
+
let db;
|
|
36
|
+
function open() {
|
|
37
|
+
if (db)
|
|
38
|
+
return db;
|
|
39
|
+
const path = dbPath();
|
|
40
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
41
|
+
db = new Database(path);
|
|
42
|
+
db.pragma("journal_mode = WAL");
|
|
43
|
+
db.exec(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
45
|
+
node_id TEXT PRIMARY KEY,
|
|
46
|
+
operator_name TEXT,
|
|
47
|
+
message TEXT,
|
|
48
|
+
first_seen_at TEXT NOT NULL,
|
|
49
|
+
last_seen_at TEXT NOT NULL
|
|
50
|
+
)
|
|
51
|
+
`);
|
|
52
|
+
return db;
|
|
53
|
+
}
|
|
54
|
+
/** Records (or refreshes) one agent.hello sighting. Idempotent per node_id. */
|
|
55
|
+
export function upsertAgent(rec) {
|
|
56
|
+
open()
|
|
57
|
+
.prepare(`INSERT INTO agents (node_id, operator_name, message, first_seen_at, last_seen_at)
|
|
58
|
+
VALUES (@node_id, @operator_name, @message, @at, @at)
|
|
59
|
+
ON CONFLICT(node_id) DO UPDATE SET
|
|
60
|
+
operator_name = excluded.operator_name,
|
|
61
|
+
message = excluded.message,
|
|
62
|
+
last_seen_at = excluded.last_seen_at`)
|
|
63
|
+
.run({
|
|
64
|
+
node_id: rec.node_id,
|
|
65
|
+
operator_name: rec.operator_name ?? null,
|
|
66
|
+
message: rec.message ?? null,
|
|
67
|
+
at: rec.at,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Removes one agent immediately -- called on receiving its agent.goodbye. */
|
|
71
|
+
export function removeAgent(nodeId) {
|
|
72
|
+
open().prepare("DELETE FROM agents WHERE node_id = ?").run(nodeId);
|
|
73
|
+
}
|
|
74
|
+
/** Most-recently-seen first. page is 1-based. */
|
|
75
|
+
export function listAgents(page, pageSize) {
|
|
76
|
+
const d = open();
|
|
77
|
+
const total = d.prepare("SELECT COUNT(*) AS n FROM agents").get().n;
|
|
78
|
+
const agents = d
|
|
79
|
+
.prepare("SELECT * FROM agents ORDER BY last_seen_at DESC LIMIT ? OFFSET ?")
|
|
80
|
+
.all(pageSize, (Math.max(1, page) - 1) * pageSize);
|
|
81
|
+
return { total, agents };
|
|
82
|
+
}
|
|
83
|
+
/** Drops agents not seen in maxAgeSeconds -- lazy cleanup, not a background timer. Returns rows removed. */
|
|
84
|
+
export function pruneStale(maxAgeSeconds) {
|
|
85
|
+
const cutoff = new Date(Date.now() - maxAgeSeconds * 1000).toISOString();
|
|
86
|
+
return open().prepare("DELETE FROM agents WHERE last_seen_at < ?").run(cutoff).changes;
|
|
87
|
+
}
|
|
88
|
+
/** Test/shutdown hook -- releases the file handle. Re-opens lazily on next use. */
|
|
89
|
+
export function closeRoster() {
|
|
90
|
+
db?.close();
|
|
91
|
+
db = undefined;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=roster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roster.js","sourceRoot":"","sources":["../src/roster.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@macula-io/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol server that exposes the Macula mesh to any agent harness, via macula-cli",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -43,9 +43,11 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
46
|
+
"better-sqlite3": "^12.11.1",
|
|
46
47
|
"zod": "^3.23.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
50
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
49
51
|
"@types/node": "^20.0.0",
|
|
50
52
|
"typescript": "^5.4.0",
|
|
51
53
|
"vitest": "^1.0.0"
|