@appsoftwareltd/etherpk-mcp 0.1.1 → 0.2.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 CHANGED
@@ -17,18 +17,18 @@ server and graph filled in. They are:
17
17
 
18
18
  ```sh
19
19
  # Once per computer. Prompts for an account-wide Personal Access Token (make one at
20
- # https://server.etherpk.com/account/tokens, also reachable from "Access tokens" in
20
+ # https://sync.etherpk.com/account/tokens, also reachable from "Access tokens" in
21
21
  # EtherPK's account menu), then shows a short code and the address of your EtherPK:
22
22
  # open EtherPK there in a browser where you're signed in with your graphs unlocked -
23
23
  # any page will do - and confirm the code. The same step as adding a phone.
24
- npx @appsoftwareltd/etherpk-mcp login --server https://server.etherpk.com
24
+ npx @appsoftwareltd/etherpk-mcp login --sync-server https://sync.etherpk.com
25
25
 
26
26
  # Self-hosting your own Sync Server? Give its address instead:
27
- # npx @appsoftwareltd/etherpk-mcp login --server https://sync.your-domain.example
27
+ # npx @appsoftwareltd/etherpk-mcp login --sync-server https://sync.your-domain.example
28
28
 
29
29
  # No EtherPK to hand on this computer (a server you reach over SSH, say)? Press r while
30
30
  # login is waiting, or use your Recovery Code from the start:
31
- # npx @appsoftwareltd/etherpk-mcp login --server https://server.etherpk.com --recovery-code
31
+ # npx @appsoftwareltd/etherpk-mcp login --sync-server https://sync.etherpk.com --recovery-code
32
32
 
33
33
  # Tell the agent about the graph (Claude Code shown; the Agents tab has the others).
34
34
  claude mcp add etherpk -- npx @appsoftwareltd/etherpk-mcp serve --graph <graph id>
@@ -37,6 +37,10 @@ claude mcp add etherpk -- npx @appsoftwareltd/etherpk-mcp serve --graph <graph i
37
37
  `npx @appsoftwareltd/etherpk-mcp graphs` lists the graphs the token can reach, by name and id.
38
38
  For a scripted setup, `ETHERPK_PAT` and `ETHERPK_RECOVERY_CODE` stand in for the prompts.
39
39
 
40
+ Every command runs through `npx`, which fetches the package but never puts `etherpk-mcp` on your
41
+ PATH. If you'd rather type the short form, `npm install -g @appsoftwareltd/etherpk-mcp` once and
42
+ drop the `npx @appsoftwareltd/` prefix; the program's own hints follow whichever way you ran it.
43
+
40
44
  ## What the agent gets
41
45
 
42
46
  `list_documents`, `read_document`, `search`, `backlinks`, `tasks`, `edit_document` (an exact,
@@ -57,5 +61,30 @@ One running instance serves one graph.
57
61
  Revoke the token at the portal to cut the agent off, and `npx @appsoftwareltd/etherpk-mcp logout`
58
62
  to forget the keys and delete the cache on that computer.
59
63
 
64
+ ## Building and publishing
65
+
66
+ From the EtherPK monorepo (`apps/mcp`; the bundle compiles the Client's own sync, crypto and
67
+ index code in through a `$lib` alias, so it builds from the repo, not from this folder alone):
68
+
69
+ ```sh
70
+ pnpm install # once, at the repo root
71
+ pnpm --filter @appsoftwareltd/etherpk-mcp check # type-check
72
+ pnpm --filter @appsoftwareltd/etherpk-mcp test # unit tests (loopback relay, no server)
73
+ pnpm --filter @appsoftwareltd/etherpk-mcp build # dist/main.js; run it with node dist/main.js
74
+ ```
75
+
76
+ To release: bump `version` in `package.json` (the CLI reads its version from there), then from
77
+ `apps/mcp` in a real terminal (both the login and the publish need a browser or one-time code,
78
+ which needs a TTY):
79
+
80
+ ```sh
81
+ npm whoami || npm login # only if not already signed in (or the token has expired); needs publish rights on @appsoftwareltd
82
+ pnpm publish --access public --no-git-checks # prepack runs the build; pnpm rewrites workspace:* deps
83
+ npx -y @appsoftwareltd/etherpk-mcp@<version> --version # verify, once the registry lists it (a few minutes)
84
+ ```
85
+
86
+ The end-to-end specs in `tests-sync/agents/` build and drive this bundle against a real Sync
87
+ Server; the technical notes are in the repo under `docs/docs/technical/Headless Client.md`.
88
+
60
89
  Full guide, including how the cache stays current and what an agent can and can't do:
61
90
  <https://docs.etherpk.com/using-ai-agents-with-your-notes>.
package/dist/main.js CHANGED
@@ -15,6 +15,61 @@ import { parser } from "@lezer/markdown";
15
15
  import { parse, stringify } from "yaml";
16
16
  import { deserialize, serialize } from "node:v8";
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
+ var package_default = {
19
+ name: "@appsoftwareltd/etherpk-mcp",
20
+ version: "0.2.0",
21
+ license: "Elastic-2.0",
22
+ description: "EtherPK Headless Client: an MCP server over a synced knowledge graph, run beside the agent on the user's own machine.",
23
+ type: "module",
24
+ "private": false,
25
+ bin: { "etherpk-mcp": "./bin/etherpk-mcp.js" },
26
+ files: ["bin", "dist"],
27
+ scripts: {
28
+ "build": "vite build",
29
+ "check": "tsc -p tsconfig.json --noEmit",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "prepack": "pnpm build"
33
+ },
34
+ dependencies: {
35
+ "@lezer/markdown": "^1.6.4",
36
+ "@modelcontextprotocol/sdk": "^1.30.0",
37
+ "@noble/curves": "^2.2.0",
38
+ "@noble/hashes": "^2.2.0",
39
+ "@sqlite.org/sqlite-wasm": "3.53.0-build1",
40
+ "fake-indexeddb": "^6.2.5",
41
+ "lib0": "^0.2.117",
42
+ "y-protocols": "^1.0.7",
43
+ "yaml": "^2.9.0",
44
+ "yjs": "^13.6.31",
45
+ "zod": "^4.3.6"
46
+ },
47
+ devDependencies: {
48
+ "@appsoftwareltd/etherpk-shared": "workspace:*",
49
+ "@codemirror/view": "^6.43.0",
50
+ "@types/node": "^25.6.0",
51
+ "typescript": "^6.0.2",
52
+ "vite": "^8.0.8",
53
+ "vitest": "^4.1.4"
54
+ },
55
+ publishConfig: { "access": "public" },
56
+ homepage: "https://docs.etherpk.com/using-ai-agents-with-your-notes",
57
+ repository: {
58
+ "type": "git",
59
+ "url": "https://github.com/appsoftwareltd/etherpk",
60
+ "directory": "apps/mcp"
61
+ },
62
+ keywords: [
63
+ "etherpk",
64
+ "mcp",
65
+ "model-context-protocol",
66
+ "knowledge-graph",
67
+ "notes",
68
+ "agent"
69
+ ],
70
+ engines: { "node": ">=22" }
71
+ };
72
+ //#endregion
18
73
  //#region ../client/src/lib/crypto/bytes.ts
19
74
  /**
20
75
  * Byte-array and encoding primitives for the crypto core. No cryptography here.
@@ -598,13 +653,13 @@ function createSyncTokenSource(mint, opts) {
598
653
  */
599
654
  function createHeadlessAccount(config) {
600
655
  const api = createSyncApi({
601
- baseUrl: config.server,
656
+ baseUrl: config.syncServer,
602
657
  token: config.pat
603
658
  });
604
659
  return {
605
660
  api,
606
- serverBaseUrl: config.server,
607
- relayUrl: relayUrlFrom(config.server),
661
+ serverBaseUrl: config.syncServer,
662
+ relayUrl: relayUrlFrom(config.syncServer),
608
663
  tokenFor: (graphId) => createSyncTokenSource(() => api.mintSyncToken(graphId))
609
664
  };
610
665
  }
@@ -674,9 +729,9 @@ function parseConfig(raw) {
674
729
  }
675
730
  if (typeof parsed !== "object" || parsed === null) return null;
676
731
  const rec = parsed;
677
- if (typeof rec.server !== "string" || typeof rec.pat !== "string") return null;
732
+ if (typeof rec.syncServer !== "string" || typeof rec.pat !== "string") return null;
678
733
  const out = {
679
- server: rec.server.replace(/\/$/, ""),
734
+ syncServer: rec.syncServer.replace(/\/$/, ""),
680
735
  pat: rec.pat
681
736
  };
682
737
  if (typeof rec.vaultKey === "string" && rec.vaultKey !== "") out.vaultKey = rec.vaultKey;
@@ -8335,7 +8390,7 @@ function createMcpServer(graph, info) {
8335
8390
  /**
8336
8391
  * `etherpk-mcp`: the [[Headless Client]]'s command line (ADR 0072).
8337
8392
  *
8338
- * etherpk-mcp login --server <url> [--pat <token>] [--recovery-code]
8393
+ * etherpk-mcp login --sync-server <url> [--pat <token>] [--recovery-code]
8339
8394
  * etherpk-mcp graphs
8340
8395
  * etherpk-mcp serve --graph <id or name>
8341
8396
  * etherpk-mcp logout
@@ -8343,22 +8398,29 @@ function createMcpServer(graph, info) {
8343
8398
  * `serve` speaks MCP over stdio, so everything for the human goes to stderr; stdout belongs
8344
8399
  * to the agent. `login` and `graphs` are interactive and print to stdout.
8345
8400
  */
8346
- var VERSION = "0.1.0";
8401
+ var VERSION = package_default.version;
8402
+ /**
8403
+ * How the user invokes this program, so every hint is one they can paste. Through npx - the way
8404
+ * the Agents tab, the README and the docs all say - the bin is never on their PATH, and an npx
8405
+ * run executes out of npm's `_npx` cache; a global install (`npm install -g`) runs from
8406
+ * anywhere else and does have `etherpk-mcp` on the PATH, so it gets the short spelling.
8407
+ */
8408
+ var CMD = /[\\/]_npx[\\/]/.test(process.argv[1] ?? "") ? "npx @appsoftwareltd/etherpk-mcp" : "etherpk-mcp";
8347
8409
  var USAGE = `etherpk-mcp - EtherPK Headless Client (an MCP server over one synced graph)
8348
8410
 
8349
- etherpk-mcp login --server <url> [--pat <token>] [--recovery-code]
8411
+ ${CMD} login --sync-server <url> [--pat <token>] [--recovery-code]
8350
8412
  Sign this machine in as a device of your account. Prompts for a Personal Access
8351
8413
  Token (an account-wide one, from the Sync Server portal at <url>/account/tokens)
8352
8414
  unless --pat or ETHERPK_PAT is given, then unlocks your keys by Device Approval:
8353
8415
  open EtherPK in a browser signed in to the account with its graphs unlocked and
8354
8416
  confirm the code shown. Press r while waiting, or pass --recovery-code, to type
8355
8417
  your Recovery Code instead (or ETHERPK_RECOVERY_CODE, for a scripted setup).
8356
- etherpk-mcp graphs
8418
+ ${CMD} graphs
8357
8419
  List the synced graphs this account can reach, by name and id.
8358
- etherpk-mcp serve --graph <id or name>
8420
+ ${CMD} serve --graph <id or name>
8359
8421
  Serve one graph to an agent over stdio. For Claude Code:
8360
8422
  claude mcp add etherpk -- npx @appsoftwareltd/etherpk-mcp serve --graph <id>
8361
- etherpk-mcp logout
8423
+ ${CMD} logout
8362
8424
  Forget the token, keys and cached graphs on this machine.
8363
8425
 
8364
8426
  The config file is ${defaultConfigPath()} (override with ETHERPK_MCP_CONFIG); cached graphs live
@@ -8409,30 +8471,30 @@ async function ask(question, { secret = false } = {}) {
8409
8471
  }
8410
8472
  async function requireConfig(path) {
8411
8473
  const config = await readConfig(path);
8412
- if (!config) fail(`Not logged in on this machine. Run: etherpk-mcp login --server <url>`);
8474
+ if (!config) fail(`Not logged in on this machine. Run: ${CMD} login --sync-server <url>`);
8413
8475
  return config;
8414
8476
  }
8415
8477
  async function login(args) {
8416
8478
  const path = defaultConfigPath();
8417
- const server = (args.server ?? (await readConfig(path))?.server ?? await ask("Sync Server URL: ")).replace(/\/$/, "");
8418
- if (!/^https?:\/\//.test(server)) fail("The server must be an http(s) URL.");
8419
- const pat = args.pat ?? process.env.ETHERPK_PAT ?? await ask(`Personal Access Token (account-wide, from ${server}/account/tokens): `, { secret: true });
8479
+ const syncServer = (args["sync-server"] ?? (await readConfig(path))?.syncServer ?? await ask("Sync Server URL: ")).replace(/\/$/, "");
8480
+ if (!/^https?:\/\//.test(syncServer)) fail("The Sync Server must be an http(s) URL.");
8481
+ const pat = args.pat ?? process.env.ETHERPK_PAT ?? await ask(`Personal Access Token (account-wide, from ${syncServer}/account/tokens): `, { secret: true });
8420
8482
  if (!pat) fail("A Personal Access Token is required.");
8421
8483
  const account = await connectAccount({
8422
- server,
8484
+ syncServer,
8423
8485
  pat
8424
8486
  });
8425
- console.log(`Signed in to ${server} as ${account.principal.email ?? account.principal.name ?? account.principal.id}.`);
8487
+ console.log(`Signed in to ${syncServer} as ${account.principal.email ?? account.principal.name ?? account.principal.id}.`);
8426
8488
  const byRecoveryCode = async () => unlockByRecoveryCode(account.api, process.env.ETHERPK_RECOVERY_CODE ?? await ask("Recovery Code: ", { secret: true }));
8427
8489
  const vaultKey = args["recovery-code"] ? await byRecoveryCode() : await approveOrFallBack(account, byRecoveryCode);
8428
8490
  await writeConfig(path, {
8429
- server,
8491
+ syncServer,
8430
8492
  pat,
8431
8493
  vaultKey: toBase64Url(vaultKey)
8432
8494
  });
8433
8495
  console.log(`Keys unlocked and cached in ${path} (owner-only). Anyone who can read your files on this machine can read this account, as with a signed-in browser.`);
8434
8496
  await listGraphs({
8435
- server,
8497
+ syncServer,
8436
8498
  pat,
8437
8499
  vaultKey: toBase64Url(vaultKey)
8438
8500
  });
@@ -8485,7 +8547,7 @@ async function approveOrFallBack(account, byRecoveryCode) {
8485
8547
  return byRecoveryCode();
8486
8548
  }
8487
8549
  async function listGraphs(config) {
8488
- if (!config.vaultKey) fail("Keys are not unlocked on this machine. Run: etherpk-mcp login");
8550
+ if (!config.vaultKey) fail(`Keys are not unlocked on this machine. Run: ${CMD} login`);
8489
8551
  const account = await connectAccount(config);
8490
8552
  const vault = await openAccountVault(account.api, fromBase64Url(config.vaultKey));
8491
8553
  const graphs = await account.api.listGraphs();
@@ -8507,13 +8569,14 @@ async function listGraphs(config) {
8507
8569
  console.log(` ${record.id} ${label} [${record.role}]`);
8508
8570
  }
8509
8571
  console.log("");
8510
- console.log("Serve one to an agent with: etherpk-mcp serve --graph <id>");
8572
+ console.log(`Serve one to an agent with: ${CMD} serve --graph <id>`);
8573
+ console.log(`For Claude Code: claude mcp add etherpk -- ${CMD} serve --graph <id>`);
8511
8574
  }
8512
8575
  async function serve(args) {
8513
8576
  const wanted = args.graph?.trim();
8514
8577
  if (!wanted) fail("serve needs --graph <id or name>.");
8515
8578
  const config = await requireConfig(defaultConfigPath());
8516
- if (!config.vaultKey) fail("Keys are not unlocked on this machine. Run: etherpk-mcp login");
8579
+ if (!config.vaultKey) fail(`Keys are not unlocked on this machine. Run: ${CMD} login`);
8517
8580
  const account = await connectAccount(config);
8518
8581
  const vault = await openAccountVault(account.api, fromBase64Url(config.vaultKey));
8519
8582
  const graphs = await account.api.listGraphs();
@@ -8535,7 +8598,7 @@ async function serve(args) {
8535
8598
  break;
8536
8599
  }
8537
8600
  }
8538
- if (!graphId) fail(`No synced graph is named or identified by "${wanted}". Run: etherpk-mcp graphs`);
8601
+ if (!graphId) fail(`No synced graph is named or identified by "${wanted}". Run: ${CMD} graphs`);
8539
8602
  const { record, keyring } = resolveGraphById(graphs, vault, graphId);
8540
8603
  console.error(`etherpk-mcp: opening graph ${graphId} on ${account.serverBaseUrl}…`);
8541
8604
  const graph = await openHeadlessGraph({
@@ -8579,7 +8642,7 @@ async function main() {
8579
8642
  args: process.argv.slice(2),
8580
8643
  allowPositionals: true,
8581
8644
  options: {
8582
- server: { type: "string" },
8645
+ "sync-server": { type: "string" },
8583
8646
  pat: { type: "string" },
8584
8647
  "recovery-code": { type: "boolean" },
8585
8648
  graph: { type: "string" },