@adapt-toolkit/a2adapt 0.3.0 → 0.4.1

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
@@ -20,7 +20,7 @@ exposes six tools — each a thin wrapper over one MUFL user transaction:
20
20
  | Env var | Default | Meaning |
21
21
  |---------|---------|---------|
22
22
  | `A2ADAPT_STATE_DIR` | `~/.a2adapt` | Node identity + serialized state. Distinct per node. |
23
- | `A2ADAPT_BROKER_URL` | `ws://localhost:9000` | The ADAPT broker to connect through. |
23
+ | `A2ADAPT_BROKER_URL` | `ws://a2adapt.adaptframework.solutions/broker` | The ADAPT broker to connect through. Set to `ws://localhost:9000` for a local broker. |
24
24
 
25
25
  ## Build
26
26
 
package/dist/cli.js CHANGED
@@ -2,15 +2,15 @@
2
2
  import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
3
3
 
4
4
  // src/cli.ts
5
- import { spawn } from "node:child_process";
5
+ import { spawn, spawnSync } from "node:child_process";
6
6
  import { connect } from "node:net";
7
- import { homedir } from "node:os";
7
+ import { homedir, userInfo } from "node:os";
8
8
  import { resolve, join, dirname } from "node:path";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
10
  import * as fs from "node:fs";
11
11
  var STATE_DIR = resolve(process.env.A2ADAPT_STATE_DIR ?? resolve(homedir(), ".a2adapt"));
12
12
  var PORT = parseInt(process.env.A2ADAPT_PORT ?? "3030", 10);
13
- var BROKER_URL = process.env.A2ADAPT_BROKER_URL ?? "wss://a2adapt.adaptframework.solutions/broker";
13
+ var BROKER_URL = process.env.A2ADAPT_BROKER_URL ?? "ws://a2adapt.adaptframework.solutions/broker";
14
14
  var PID_PATH = join(STATE_DIR, "daemon.pid");
15
15
  var LOG_PATH = join(STATE_DIR, "daemon.log");
16
16
  var SELF = fileURLToPath(import.meta.url);
@@ -133,6 +133,11 @@ async function cmdStop() {
133
133
  async function cmdStatus() {
134
134
  const pid = runningPid();
135
135
  if (!pid) {
136
+ if (await portOpen(PORT)) {
137
+ out("a2adapt-mcp: running (no pidfile \u2014 likely a stale process or external launcher)");
138
+ out(` url: http://localhost:${PORT}/mcp (reachable)`);
139
+ return;
140
+ }
136
141
  out("a2adapt-mcp: stopped");
137
142
  process.exitCode = 1;
138
143
  return;
@@ -145,6 +150,196 @@ async function cmdStatus() {
145
150
  out(` state: ${STATE_DIR}`);
146
151
  out(` logs: ${LOG_PATH}`);
147
152
  }
153
+ function cmdWatch(which) {
154
+ const offsets = /* @__PURE__ */ new Map();
155
+ const scan = (initial) => {
156
+ let names;
157
+ try {
158
+ names = fs.readdirSync(STATE_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
159
+ } catch {
160
+ return;
161
+ }
162
+ for (const name of names) {
163
+ if (which && name !== which) continue;
164
+ const logPath = join(STATE_DIR, name, "inbox.log");
165
+ let size;
166
+ try {
167
+ size = fs.statSync(logPath).size;
168
+ } catch {
169
+ continue;
170
+ }
171
+ let seen = offsets.get(logPath);
172
+ if (seen === void 0) {
173
+ seen = initial ? size : 0;
174
+ offsets.set(logPath, seen);
175
+ if (initial) continue;
176
+ }
177
+ if (size <= seen) {
178
+ if (size < seen) offsets.set(logPath, size);
179
+ continue;
180
+ }
181
+ let chunk;
182
+ try {
183
+ const fd = fs.openSync(logPath, "r");
184
+ const buf = Buffer.alloc(size - seen);
185
+ fs.readSync(fd, buf, 0, buf.length, seen);
186
+ fs.closeSync(fd);
187
+ chunk = buf.toString("utf8");
188
+ } catch {
189
+ continue;
190
+ }
191
+ offsets.set(logPath, size);
192
+ for (const line of chunk.split("\n")) {
193
+ if (!line.trim()) continue;
194
+ let msg;
195
+ try {
196
+ msg = JSON.parse(line);
197
+ } catch {
198
+ continue;
199
+ }
200
+ out(
201
+ `[${name}] new message from ${msg.sender ?? "?"}: ${msg.text ?? ""}` + (msg.date ? ` (${msg.date})` : "")
202
+ );
203
+ }
204
+ }
205
+ };
206
+ err(
207
+ `a2adapt-mcp watch: watching ${which ? `identity "${which}"` : "all identities"} under ${STATE_DIR} (Ctrl-C to stop)`
208
+ );
209
+ scan(true);
210
+ const timer = setInterval(() => scan(false), 1e3);
211
+ const stop = () => {
212
+ clearInterval(timer);
213
+ process.exit(0);
214
+ };
215
+ process.on("SIGINT", stop);
216
+ process.on("SIGTERM", stop);
217
+ }
218
+ var SYSTEMD_UNIT = "a2adapt.service";
219
+ var LAUNCHD_LABEL = "solutions.adaptframework.a2adapt";
220
+ function systemdUnitPath() {
221
+ return join(homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
222
+ }
223
+ function launchdPlistPath() {
224
+ return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
225
+ }
226
+ function run(cmd, args) {
227
+ const r = spawnSync(cmd, args, { stdio: "inherit" });
228
+ return r.status === 0;
229
+ }
230
+ function installSystemd() {
231
+ const unitPath = systemdUnitPath();
232
+ fs.mkdirSync(dirname(unitPath), { recursive: true });
233
+ const unit = `[Unit]
234
+ Description=a2adapt MCP daemon (secure agent-to-agent messaging over ADAPT)
235
+ After=network-online.target
236
+ Wants=network-online.target
237
+
238
+ [Service]
239
+ Type=simple
240
+ ExecStart=${process.execPath} ${SELF} serve
241
+ Environment=A2ADAPT_TRANSPORT=http
242
+ Environment=A2ADAPT_PORT=${PORT}
243
+ Environment=A2ADAPT_BROKER_URL=${BROKER_URL}
244
+ Environment=A2ADAPT_STATE_DIR=${STATE_DIR}
245
+ Restart=on-failure
246
+ RestartSec=2
247
+
248
+ [Install]
249
+ WantedBy=default.target
250
+ `;
251
+ fs.writeFileSync(unitPath, unit);
252
+ out(`wrote ${unitPath}`);
253
+ run("systemctl", ["--user", "daemon-reload"]);
254
+ if (!run("systemctl", ["--user", "enable", "--now", SYSTEMD_UNIT])) {
255
+ err("failed to enable/start the service via systemctl --user.");
256
+ process.exit(1);
257
+ }
258
+ if (!run("loginctl", ["enable-linger", userInfo().username])) {
259
+ err("warning: could not enable linger \u2014 the daemon may not start until you log in.");
260
+ err(` run manually: loginctl enable-linger ${userInfo().username}`);
261
+ }
262
+ out("");
263
+ out(`a2adapt-mcp installed as a systemd user service and started.`);
264
+ out(` status: systemctl --user status ${SYSTEMD_UNIT}`);
265
+ out(` logs: journalctl --user -u ${SYSTEMD_UNIT} -f`);
266
+ out(` remove: a2adapt-mcp uninstall-service`);
267
+ }
268
+ function uninstallSystemd() {
269
+ run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
270
+ const unitPath = systemdUnitPath();
271
+ try {
272
+ fs.rmSync(unitPath, { force: true });
273
+ out(`removed ${unitPath}`);
274
+ } catch (e) {
275
+ err(`failed to remove ${unitPath}: ${String(e)}`);
276
+ }
277
+ run("systemctl", ["--user", "daemon-reload"]);
278
+ out("a2adapt-mcp service uninstalled.");
279
+ }
280
+ function installLaunchd() {
281
+ const plistPath = launchdPlistPath();
282
+ fs.mkdirSync(dirname(plistPath), { recursive: true });
283
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
284
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
285
+ <plist version="1.0">
286
+ <dict>
287
+ <key>Label</key><string>${LAUNCHD_LABEL}</string>
288
+ <key>ProgramArguments</key>
289
+ <array>
290
+ <string>${process.execPath}</string>
291
+ <string>${SELF}</string>
292
+ <string>serve</string>
293
+ </array>
294
+ <key>EnvironmentVariables</key>
295
+ <dict>
296
+ <key>A2ADAPT_TRANSPORT</key><string>http</string>
297
+ <key>A2ADAPT_PORT</key><string>${PORT}</string>
298
+ <key>A2ADAPT_BROKER_URL</key><string>${BROKER_URL}</string>
299
+ <key>A2ADAPT_STATE_DIR</key><string>${STATE_DIR}</string>
300
+ </dict>
301
+ <key>RunAtLoad</key><true/>
302
+ <key>KeepAlive</key><true/>
303
+ <key>StandardOutPath</key><string>${LOG_PATH}</string>
304
+ <key>StandardErrorPath</key><string>${LOG_PATH}</string>
305
+ </dict>
306
+ </plist>
307
+ `;
308
+ fs.writeFileSync(plistPath, plist);
309
+ out(`wrote ${plistPath}`);
310
+ run("launchctl", ["unload", plistPath]);
311
+ if (!run("launchctl", ["load", "-w", plistPath])) {
312
+ err("failed to load the launchd agent.");
313
+ process.exit(1);
314
+ }
315
+ out("");
316
+ out("a2adapt-mcp installed as a launchd agent and started.");
317
+ out(` remove: a2adapt-mcp uninstall-service`);
318
+ }
319
+ function uninstallLaunchd() {
320
+ const plistPath = launchdPlistPath();
321
+ run("launchctl", ["unload", plistPath]);
322
+ try {
323
+ fs.rmSync(plistPath, { force: true });
324
+ out(`removed ${plistPath}`);
325
+ } catch (e) {
326
+ err(`failed to remove ${plistPath}: ${String(e)}`);
327
+ }
328
+ out("a2adapt-mcp service uninstalled.");
329
+ }
330
+ async function cmdInstallService() {
331
+ await cmdStop();
332
+ if (process.platform === "linux") return installSystemd();
333
+ if (process.platform === "darwin") return installLaunchd();
334
+ err(`install-service: unsupported platform "${process.platform}" (only linux/systemd and macOS/launchd).`);
335
+ process.exit(1);
336
+ }
337
+ function cmdUninstallService() {
338
+ if (process.platform === "linux") return uninstallSystemd();
339
+ if (process.platform === "darwin") return uninstallLaunchd();
340
+ err(`uninstall-service: unsupported platform "${process.platform}".`);
341
+ process.exit(1);
342
+ }
148
343
  function usage() {
149
344
  out("a2adapt-mcp \u2014 daemon for the a2adapt MCP server");
150
345
  out("");
@@ -154,8 +349,13 @@ function usage() {
154
349
  out(" restart stop then start");
155
350
  out(" status show whether the daemon is running");
156
351
  out(" serve run in the foreground (used by start; handy for debugging)");
352
+ out(" watch [identity] stream one line per new inbound message (wake source for a Monitor)");
353
+ out("");
354
+ out(" install-service install + start a boot-persistent service (systemd/launchd)");
355
+ out(" uninstall-service stop + remove that service");
157
356
  out("");
158
357
  out("Config (env): A2ADAPT_BROKER_URL, A2ADAPT_PORT (3030), A2ADAPT_STATE_DIR (~/.a2adapt)");
358
+ out("(install-service bakes the current config values into the service definition.)");
159
359
  }
160
360
  async function main() {
161
361
  const cmd = process.argv[2] ?? "help";
@@ -163,6 +363,23 @@ async function main() {
163
363
  case "serve":
164
364
  case "run":
165
365
  if (!process.env.A2ADAPT_TRANSPORT) process.env.A2ADAPT_TRANSPORT = "http";
366
+ fs.mkdirSync(STATE_DIR, { recursive: true });
367
+ fs.writeFileSync(PID_PATH, String(process.pid));
368
+ {
369
+ const cleanup = () => {
370
+ try {
371
+ fs.rmSync(PID_PATH, { force: true });
372
+ } catch {
373
+ }
374
+ };
375
+ process.on("exit", cleanup);
376
+ for (const sig of ["SIGTERM", "SIGINT"]) {
377
+ process.on(sig, () => {
378
+ cleanup();
379
+ process.exit(0);
380
+ });
381
+ }
382
+ }
166
383
  await import(pathToFileURL(join(dirname(SELF), "index.js")).href);
167
384
  break;
168
385
  case "start":
@@ -178,6 +395,15 @@ async function main() {
178
395
  case "status":
179
396
  await cmdStatus();
180
397
  break;
398
+ case "watch":
399
+ cmdWatch(process.argv[3]);
400
+ break;
401
+ case "install-service":
402
+ await cmdInstallService();
403
+ break;
404
+ case "uninstall-service":
405
+ cmdUninstallService();
406
+ break;
181
407
  case "help":
182
408
  case "--help":
183
409
  case "-h":
@@ -2,24 +2,122 @@
2
2
  import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
3
3
 
4
4
  // src/hooks/runner.ts
5
- async function sessionStart() {
5
+ import * as fs from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { resolve, join } from "node:path";
8
+ var STATE_DIR = resolve(
9
+ process.env.A2ADAPT_STATE_DIR ?? resolve(homedir(), ".a2adapt")
10
+ );
11
+ function readStdin() {
12
+ try {
13
+ return fs.readFileSync(0, "utf8");
14
+ } catch {
15
+ return "";
16
+ }
17
+ }
18
+ function emit(payload) {
19
+ process.stdout.write(JSON.stringify(payload));
6
20
  }
7
- async function userPromptSubmit() {
21
+ function noop() {
22
+ emit({ continue: true });
8
23
  }
9
- async function main() {
24
+ function readCursor(dir) {
25
+ try {
26
+ return parseInt(fs.readFileSync(join(dir, "inbox_cursor"), "utf8").trim(), 10) || 0;
27
+ } catch {
28
+ return 0;
29
+ }
30
+ }
31
+ function readInboxLog(dir) {
32
+ let raw;
33
+ try {
34
+ raw = fs.readFileSync(join(dir, "inbox.log"), "utf8");
35
+ } catch {
36
+ return [];
37
+ }
38
+ const msgs = [];
39
+ for (const line of raw.split("\n")) {
40
+ if (!line.trim()) continue;
41
+ try {
42
+ const m = JSON.parse(line);
43
+ msgs.push({
44
+ sender: String(m.sender ?? "?"),
45
+ text: String(m.text ?? ""),
46
+ date: String(m.date ?? "")
47
+ });
48
+ } catch {
49
+ }
50
+ }
51
+ return msgs;
52
+ }
53
+ function collectUnread() {
54
+ let names;
55
+ try {
56
+ names = fs.readdirSync(STATE_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
57
+ } catch {
58
+ return [];
59
+ }
60
+ const out = [];
61
+ for (const name of names) {
62
+ const dir = join(STATE_DIR, name);
63
+ const all = readInboxLog(dir);
64
+ if (all.length === 0) continue;
65
+ const unread = all.slice(readCursor(dir));
66
+ if (unread.length === 0) continue;
67
+ out.push({ name, count: unread.length, messages: unread });
68
+ }
69
+ return out;
70
+ }
71
+ function renderContext(unread) {
72
+ const total = unread.reduce((n, u) => n + u.count, 0);
73
+ const lines = [];
74
+ for (const u of unread) {
75
+ lines.push(`\u2022 ${u.name} \u2014 ${u.count} unread:`);
76
+ for (const m of u.messages.slice(-5)) {
77
+ lines.push(` [${m.sender}] ${m.text}${m.date ? ` (${m.date})` : ""}`);
78
+ }
79
+ if (u.count > 5) lines.push(` \u2026and ${u.count - 5} earlier`);
80
+ }
81
+ return `a2adapt \u2014 ${total} unread message(s) across ${unread.length} identit${unread.length === 1 ? "y" : "ies"} (arrived while you were away):
82
+ ${lines.join("\n")}
83
+
84
+ To read & clear: choose_identity({ name }) then process_incoming_message(). To wait for live replies, arm a Monitor on the wake source \`a2adapt-mcp watch\` (each new-mail line wakes you).`;
85
+ }
86
+ function sessionStart() {
87
+ const raw = readStdin();
88
+ let source = "";
89
+ if (raw) {
90
+ try {
91
+ source = JSON.parse(raw).source ?? "";
92
+ } catch {
93
+ }
94
+ }
95
+ if (source === "compact") return noop();
96
+ const unread = collectUnread();
97
+ if (unread.length === 0) return noop();
98
+ emit({
99
+ continue: true,
100
+ hookSpecificOutput: {
101
+ hookEventName: "SessionStart",
102
+ additionalContext: renderContext(unread)
103
+ }
104
+ });
105
+ }
106
+ function main() {
10
107
  const kind = process.argv[2] ?? "";
11
- switch (kind) {
12
- case "session-start":
13
- await sessionStart();
14
- break;
15
- case "user-prompt-submit":
16
- await userPromptSubmit();
17
- break;
18
- default:
19
- break;
20
- }
21
- }
22
- main().catch((err) => {
23
- process.stderr.write(`a2adapt hook: ${err?.stack ?? err}
108
+ try {
109
+ switch (kind) {
110
+ case "session-start":
111
+ sessionStart();
112
+ return;
113
+ default:
114
+ noop();
115
+ return;
116
+ }
117
+ } catch (err) {
118
+ process.stderr.write(`a2adapt hook: ${err?.stack ?? err}
24
119
  `);
25
- }).finally(() => process.exit(0));
120
+ noop();
121
+ }
122
+ }
123
+ main();
package/dist/index.js CHANGED
@@ -22436,11 +22436,11 @@ import * as fs from "node:fs";
22436
22436
  import { adapt_wrapper } from "@adapt-toolkit/sdk/executables";
22437
22437
  import { PacketWrapperConfigurator } from "@adapt-toolkit/sdk/wrappers";
22438
22438
  import { object_to_adapt_value } from "@adapt-toolkit/sdk/wrapper";
22439
- var VERSION = true ? "0.3.0" : "0.0.0-dev";
22439
+ var VERSION = true ? "0.4.1" : "0.0.0-dev";
22440
22440
  var STATE_DIR = resolve(
22441
22441
  process.env.A2ADAPT_STATE_DIR ?? resolve(homedir(), ".a2adapt")
22442
22442
  );
22443
- var BROKER_URL = process.env.A2ADAPT_BROKER_URL ?? "wss://a2adapt.adaptframework.solutions/broker";
22443
+ var BROKER_URL = process.env.A2ADAPT_BROKER_URL ?? "ws://a2adapt.adaptframework.solutions/broker";
22444
22444
  var TRANSPORT = process.env.A2ADAPT_TRANSPORT ?? "http";
22445
22445
  var PORT = parseInt(process.env.A2ADAPT_PORT ?? "3030", 10);
22446
22446
  var log = (...parts) => process.stderr.write(`a2adapt: ${parts.join(" ")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adapt-toolkit/a2adapt",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "MCP server daemon for a2adapt — one native ADAPT wrapper hosting N self-sovereign identities, exposing secure agent-to-agent messaging tools over HTTP (Streamable HTTP). Run `a2adapt-mcp start`.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,8 +49,8 @@
49
49
  "typecheck": "tsc -p tsconfig.json --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@adapt-toolkit/sdk": "^0.2.1",
53
- "@adapt-toolkit/sdk-native": "^0.2.1"
52
+ "@adapt-toolkit/sdk": "^0.2.4",
53
+ "@adapt-toolkit/sdk-native": "^0.2.3"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@modelcontextprotocol/sdk": "^1.0.4",