projectionlab-mcp 1.1.0 → 1.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.
Files changed (73) hide show
  1. package/HOW-IT-WORKS.md +2 -2
  2. package/README.md +1 -0
  3. package/REFERENCE.md +11 -2
  4. package/dist/bootstrap.js +5 -3
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/bridge.d.ts +2 -0
  7. package/dist/config.d.ts +4 -0
  8. package/dist/config.js +41 -6
  9. package/dist/config.js.map +1 -1
  10. package/dist/errors.d.ts +6 -0
  11. package/dist/errors.js +10 -0
  12. package/dist/errors.js.map +1 -1
  13. package/dist/extensionBridge.d.ts +14 -1
  14. package/dist/extensionBridge.js +74 -20
  15. package/dist/extensionBridge.js.map +1 -1
  16. package/dist/index.js +9 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/instructions.d.ts +1 -1
  19. package/dist/instructions.js +1 -1
  20. package/dist/largeOutput.js +25 -0
  21. package/dist/largeOutput.js.map +1 -1
  22. package/dist/logger.js +11 -4
  23. package/dist/logger.js.map +1 -1
  24. package/dist/pathPatch.js +5 -0
  25. package/dist/pathPatch.js.map +1 -1
  26. package/dist/plans.d.ts +45 -10
  27. package/dist/plans.js +139 -42
  28. package/dist/plans.js.map +1 -1
  29. package/dist/redact.d.ts +24 -0
  30. package/dist/redact.js +91 -0
  31. package/dist/redact.js.map +1 -1
  32. package/dist/resources/planData.js +3 -1
  33. package/dist/resources/planData.js.map +1 -1
  34. package/dist/resources/summary.js +4 -1
  35. package/dist/resources/summary.js.map +1 -1
  36. package/dist/snapshot.js +18 -11
  37. package/dist/snapshot.js.map +1 -1
  38. package/dist/tools/clonePlan.js +3 -3
  39. package/dist/tools/clonePlan.js.map +1 -1
  40. package/dist/tools/connection.js +21 -3
  41. package/dist/tools/connection.js.map +1 -1
  42. package/dist/tools/diff.js +7 -7
  43. package/dist/tools/diff.js.map +1 -1
  44. package/dist/tools/getPlan.js +4 -4
  45. package/dist/tools/getPlan.js.map +1 -1
  46. package/dist/tools/getPlanSection.js +5 -5
  47. package/dist/tools/getPlanSection.js.map +1 -1
  48. package/dist/tools/getPlanSettings.js +3 -3
  49. package/dist/tools/getPlanSettings.js.map +1 -1
  50. package/dist/tools/restore.js +18 -1
  51. package/dist/tools/restore.js.map +1 -1
  52. package/dist/tools/snapshots.js +17 -1
  53. package/dist/tools/snapshots.js.map +1 -1
  54. package/dist/tools/updatePlanEvent.js +63 -37
  55. package/dist/tools/updatePlanEvent.js.map +1 -1
  56. package/dist/tools/updatePlanSection.js +11 -6
  57. package/dist/tools/updatePlanSection.js.map +1 -1
  58. package/dist/tools/updatePlanSettings.js +3 -3
  59. package/dist/tools/updatePlanSettings.js.map +1 -1
  60. package/dist/types/wire.d.ts +27 -2
  61. package/dist/types/wire.js +6 -2
  62. package/dist/types/wire.js.map +1 -1
  63. package/dist/updateCheck.d.ts +22 -0
  64. package/dist/updateCheck.js +58 -0
  65. package/dist/updateCheck.js.map +1 -0
  66. package/dist/versionCompare.d.ts +5 -0
  67. package/dist/versionCompare.js +33 -0
  68. package/dist/versionCompare.js.map +1 -0
  69. package/dist/versionNotices.d.ts +9 -0
  70. package/dist/versionNotices.js +42 -0
  71. package/dist/versionNotices.js.map +1 -0
  72. package/package.json +1 -1
  73. package/skills/projectionlab/SKILL.md +55 -15
@@ -7,7 +7,9 @@
7
7
  *
8
8
  * Transport: JSON text frames over ws://127.0.0.1:<port>. The browser side is
9
9
  * always the WebSocket client. The server accepts one active executor
10
- * connection at a time (newest wins).
10
+ * connection at a time. The incumbent keeps it: a second authenticated
11
+ * executor is refused with 4004 and retries until the incumbent goes away,
12
+ * unless it requests takeover or repeats the incumbent's instanceId.
11
13
  */
12
14
  /** Bumped on any breaking change to frame shapes or semantics. */
13
15
  export const PROTOCOL_VERSION = 1;
@@ -21,7 +23,9 @@ export const CLOSE_CODES = {
21
23
  AUTH_FAILED: 4001,
22
24
  /** Protocol version mismatch. */
23
25
  VERSION_MISMATCH: 4002,
24
- /** A newer executor connected and replaced this one. */
26
+ /** A takeover replaced this executor; retry slowly, the next attempt normally gets BUSY. */
25
27
  REPLACED: 4003,
28
+ /** Another executor holds the connection; retry later. */
29
+ BUSY: 4004,
26
30
  };
27
31
  //# sourceMappingURL=wire.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/types/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,kFAAkF;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAgF5C,gDAAgD;AAChD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,8FAA8F;IAC9F,WAAW,EAAE,IAAI;IACjB,iCAAiC;IACjC,gBAAgB,EAAE,IAAI;IACtB,wDAAwD;IACxD,QAAQ,EAAE,IAAI;CACN,CAAC"}
1
+ {"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/types/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,kFAAkF;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAqG5C,gDAAgD;AAChD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,8FAA8F;IAC9F,WAAW,EAAE,IAAI;IACjB,iCAAiC;IACjC,gBAAgB,EAAE,IAAI;IACtB,4FAA4F;IAC5F,QAAQ,EAAE,IAAI;IACd,0DAA0D;IAC1D,IAAI,EAAE,IAAI;CACF,CAAC"}
@@ -0,0 +1,22 @@
1
+ export type FetchLike = (url: string, init: {
2
+ headers: Record<string, string>;
3
+ signal: AbortSignal;
4
+ }) => Promise<{
5
+ ok: boolean;
6
+ json(): Promise<unknown>;
7
+ }>;
8
+ /**
9
+ * Read the `version` field of the registry's latest-release document.
10
+ * Any failure (offline, timeout, non-200, bad JSON, missing or unparsable
11
+ * version) resolves to null; this never rejects.
12
+ */
13
+ export declare function fetchLatestVersion(fetchImpl?: FetchLike, timeoutMs?: number): Promise<string | null>;
14
+ export interface UpdateCheck {
15
+ /** Start the lookup; without PL_UPDATE_CHECK=false and at most once. */
16
+ start(fetchImpl?: FetchLike): void;
17
+ /** The latest version found, or null while unknown or disabled. */
18
+ latest(): string | null;
19
+ }
20
+ /** A fresh holder; the module-level one is what the server uses. */
21
+ export declare function createUpdateCheck(): UpdateCheck;
22
+ export declare const updateCheck: UpdateCheck;
@@ -0,0 +1,58 @@
1
+ // One request per server process asks the npm registry for the latest
2
+ // published version of this package, so check_connection can tell the user
3
+ // an update is available. The request carries no user data: no query string,
4
+ // no custom headers beyond Accept, nothing from config or the plan.
5
+ // PL_UPDATE_CHECK=false turns the lookup off entirely.
6
+ import { config } from "./config.js";
7
+ import { log } from "./logger.js";
8
+ import { compareVersions } from "./versionCompare.js";
9
+ import { VERSION } from "./version.js";
10
+ const REGISTRY_URL = "https://registry.npmjs.org/projectionlab-mcp/latest";
11
+ const DEFAULT_TIMEOUT_MS = 3000;
12
+ /**
13
+ * Read the `version` field of the registry's latest-release document.
14
+ * Any failure (offline, timeout, non-200, bad JSON, missing or unparsable
15
+ * version) resolves to null; this never rejects.
16
+ */
17
+ export async function fetchLatestVersion(fetchImpl = fetch, timeoutMs = DEFAULT_TIMEOUT_MS) {
18
+ try {
19
+ const response = await fetchImpl(REGISTRY_URL, {
20
+ headers: { Accept: "application/json" },
21
+ signal: AbortSignal.timeout(timeoutMs),
22
+ });
23
+ if (!response.ok)
24
+ return null;
25
+ const body = (await response.json());
26
+ if (typeof body?.version !== "string")
27
+ return null;
28
+ // Unparsable versions are as good as unknown.
29
+ return compareVersions(body.version, "0.0.0") === null ? null : body.version;
30
+ }
31
+ catch (err) {
32
+ log.debug("updateCheck", `update lookup failed: ${err}`);
33
+ return null;
34
+ }
35
+ }
36
+ /** A fresh holder; the module-level one is what the server uses. */
37
+ export function createUpdateCheck() {
38
+ let latest = null;
39
+ let started = false;
40
+ return {
41
+ start(fetchImpl = fetch) {
42
+ if (started || !config.updateCheck)
43
+ return;
44
+ started = true;
45
+ void fetchLatestVersion(fetchImpl).then((found) => {
46
+ latest = found;
47
+ if (found && compareVersions(found, VERSION) === 1) {
48
+ log.info("updateCheck", `projectionlab-mcp ${found} is available (running ${VERSION})`);
49
+ }
50
+ });
51
+ },
52
+ latest() {
53
+ return latest;
54
+ },
55
+ };
56
+ }
57
+ export const updateCheck = createUpdateCheck();
58
+ //# sourceMappingURL=updateCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateCheck.js","sourceRoot":"","sources":["../src/updateCheck.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,2EAA2E;AAC3E,6EAA6E;AAC7E,oEAAoE;AACpE,uDAAuD;AAEvD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,YAAY,GAAG,qDAAqD,CAAC;AAC3E,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAUhC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAuB,KAA6B,EACpD,YAAoB,kBAAkB;IAEtC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE;YAC7C,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA0B,CAAC;QAC9D,IAAI,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnD,8CAA8C;QAC9C,OAAO,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,yBAAyB,GAAG,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AASD,oEAAoE;AACpE,MAAM,UAAU,iBAAiB;IAC/B,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO;QACL,KAAK,CAAC,YAAuB,KAA6B;YACxD,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW;gBAAE,OAAO;YAC3C,OAAO,GAAG,IAAI,CAAC;YACf,KAAK,kBAAkB,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBAChD,MAAM,GAAG,KAAK,CAAC;gBACf,IAAI,KAAK,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACnD,GAAG,CAAC,IAAI,CACN,aAAa,EACb,qBAAqB,KAAK,0BAA0B,OAAO,GAAG,CAC/D,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAM;YACJ,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Compare two `major.minor.patch` versions numerically. Returns -1, 0, or 1,
3
+ * or null when either input is not three dot-separated non-negative integers.
4
+ */
5
+ export declare function compareVersions(a: string, b: string): -1 | 0 | 1 | null;
@@ -0,0 +1,33 @@
1
+ // Release versions are major.minor.patch triples of non-negative integers.
2
+ // Anything else (a leading v, prerelease or build suffixes, extra segments)
3
+ // is unparsable, and callers treat unparsable as "say nothing".
4
+ function parse(version) {
5
+ if (typeof version !== "string")
6
+ return null;
7
+ const parts = version.split(".");
8
+ if (parts.length !== 3)
9
+ return null;
10
+ const numbers = [];
11
+ for (const part of parts) {
12
+ if (!/^(0|[1-9]\d*)$/.test(part))
13
+ return null;
14
+ numbers.push(Number(part));
15
+ }
16
+ return [numbers[0], numbers[1], numbers[2]];
17
+ }
18
+ /**
19
+ * Compare two `major.minor.patch` versions numerically. Returns -1, 0, or 1,
20
+ * or null when either input is not three dot-separated non-negative integers.
21
+ */
22
+ export function compareVersions(a, b) {
23
+ const left = parse(a);
24
+ const right = parse(b);
25
+ if (!left || !right)
26
+ return null;
27
+ for (let i = 0; i < 3; i++) {
28
+ if (left[i] !== right[i])
29
+ return left[i] < right[i] ? -1 : 1;
30
+ }
31
+ return 0;
32
+ }
33
+ //# sourceMappingURL=versionCompare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionCompare.js","sourceRoot":"","sources":["../src/versionCompare.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,gEAAgE;AAEhE,SAAS,KAAK,CAAC,OAAe;IAC5B,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface VersionInfo {
2
+ /** The server's own release. */
3
+ server: string;
4
+ /** Connected executor, "extension/<v>" or "bootstrap/<v>", or null. */
5
+ executor: string | null;
6
+ /** Newest release on npm, or null when unknown or the check is off. */
7
+ latest: string | null;
8
+ }
9
+ export declare function versionNotices(info: VersionInfo): string[];
@@ -0,0 +1,42 @@
1
+ // Plain-language update notices for projectionlab_check_connection. Each
2
+ // rule says nothing whenever a needed comparison is unparsable or a version
3
+ // is unknown; being out of date is never reported as a connection failure.
4
+ import { compareVersions } from "./versionCompare.js";
5
+ const SERVER_FIX = "To update it, fully quit and reopen the AI assistant's host so npx fetches the new release; " +
6
+ "if the old version is still reported afterwards, set the server args in the host " +
7
+ "configuration to -y projectionlab-mcp@latest.";
8
+ const EXTENSION_FIX = "To update it, download the zip for the browser from " +
9
+ "https://github.com/Backroads4Me/projectionlab-mcp/releases/latest, replace the unpacked " +
10
+ "extension folder with its contents, and reload the extension from the browser's extensions " +
11
+ "page. Settings are kept.";
12
+ export function versionNotices(info) {
13
+ const notices = [];
14
+ const serverOutOfDate = info.latest !== null && compareVersions(info.latest, info.server) === 1;
15
+ if (serverOutOfDate) {
16
+ notices.push(`The ProjectionLab MCP server is running version ${info.server}, but version ` +
17
+ `${info.latest} is available on npm. ${SERVER_FIX}`);
18
+ }
19
+ if (info.executor) {
20
+ const slash = info.executor.lastIndexOf("/");
21
+ const kind = slash === -1 ? "" : info.executor.slice(0, slash);
22
+ const version = slash === -1 ? "" : info.executor.slice(slash + 1);
23
+ const cmp = compareVersions(version, info.server);
24
+ if (kind === "extension" && cmp === -1) {
25
+ notices.push(`The ProjectionLab browser extension is version ${version}, older than the server ` +
26
+ `(${info.server}). ${EXTENSION_FIX}`);
27
+ }
28
+ else if (kind === "extension" && cmp === 1 && !serverOutOfDate) {
29
+ // Only worth saying when npm has nothing newer to offer as well.
30
+ notices.push(`The ProjectionLab MCP server is version ${info.server}, older than the browser ` +
31
+ `extension (${version}). ${SERVER_FIX}`);
32
+ }
33
+ else if (kind === "bootstrap" && cmp !== null && cmp !== 0) {
34
+ notices.push(`The bootstrap snippet in the ProjectionLab tab is version ${version}, pasted from a ` +
35
+ `different server release than the running server (${info.server}). Ask the user to ` +
36
+ `reload the tab, then call projectionlab_get_bootstrap and paste the fresh snippet ` +
37
+ `into the browser console.`);
38
+ }
39
+ }
40
+ return notices;
41
+ }
42
+ //# sourceMappingURL=versionNotices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionNotices.js","sourceRoot":"","sources":["../src/versionNotices.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4EAA4E;AAC5E,2EAA2E;AAE3E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,UAAU,GACd,8FAA8F;IAC9F,mFAAmF;IACnF,+CAA+C,CAAC;AAClD,MAAM,aAAa,GACjB,sDAAsD;IACtD,0FAA0F;IAC1F,6FAA6F;IAC7F,0BAA0B,CAAC;AAW7B,MAAM,UAAU,cAAc,CAAC,IAAiB;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,eAAe,GACnB,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1E,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CACV,mDAAmD,IAAI,CAAC,MAAM,gBAAgB;YAC5E,GAAG,IAAI,CAAC,MAAM,yBAAyB,UAAU,EAAE,CACtD,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/D,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACnE,MAAM,GAAG,GAAG,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,IAAI,KAAK,WAAW,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,IAAI,CACV,kDAAkD,OAAO,0BAA0B;gBACjF,IAAI,IAAI,CAAC,MAAM,MAAM,aAAa,EAAE,CACvC,CAAC;QACJ,CAAC;aAAM,IAAI,IAAI,KAAK,WAAW,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YACjE,iEAAiE;YACjE,OAAO,CAAC,IAAI,CACV,2CAA2C,IAAI,CAAC,MAAM,2BAA2B;gBAC/E,cAAc,OAAO,MAAM,UAAU,EAAE,CAC1C,CAAC;QACJ,CAAC;aAAM,IAAI,IAAI,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YAC7D,OAAO,CAAC,IAAI,CACV,6DAA6D,OAAO,kBAAkB;gBACpF,qDAAqD,IAAI,CAAC,MAAM,qBAAqB;gBACrF,oFAAoF;gBACpF,2BAA2B,CAC9B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectionlab-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for ProjectionLab: read and safely edit your retirement plan from any MCP-compatible AI assistant",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,9 +21,22 @@ also serves this guide as the MCP resource `projectionlab://guide`.
21
21
  ## Start every session with a connection check
22
22
 
23
23
  Call `projectionlab_check_connection` before any other ProjectionLab tool. It
24
- returns `browser`, `pageLoaded`, `apiAvailable`, `apiKeyValid`, `errors` and
25
- `writesEnabled`. When the first four flags are `true`, proceed. Otherwise fix
26
- the first failing item with the user, one step at a time, then check again.
24
+ returns `browser`, `pageLoaded`, `apiAvailable`, `apiKeyValid`, `errors`,
25
+ `writesEnabled`, `versions` and `notices`. When the first four flags are
26
+ `true`, proceed. Otherwise fix the first failing item with the user, one step
27
+ at a time, then check again.
28
+
29
+ `notices` are plain sentences about releases, never connection failures. When
30
+ the list is not empty, tell the user each one once, in plain words, then
31
+ carry on; do not repeat them later in the session and do not refuse to work
32
+ because of them. The two fixes: an out-of-date server is updated by fully
33
+ quitting and reopening the AI assistant's host so `npx` fetches the new
34
+ release (if it still reports the old version, set the server args to
35
+ `["-y", "projectionlab-mcp@latest"]`); an out-of-date extension is updated by
36
+ downloading the browser zip from
37
+ `https://github.com/Backroads4Me/projectionlab-mcp/releases/latest`,
38
+ replacing the unpacked extension folder with its contents, and reloading the
39
+ extension from the browser's extensions page (settings are kept).
27
40
 
28
41
  | Failing item | What it means | What to do with the user |
29
42
  |---|---|---|
@@ -66,6 +79,11 @@ Recommended, persistent: the **MCP Bridge for ProjectionLab** browser extension.
66
79
  check.
67
80
  4. Open https://app.projectionlab.com and log in.
68
81
 
82
+ If another browser is already connected to the server, the status reads
83
+ **Standing by** instead: that extension connects on its own when the other
84
+ browser disconnects, and pressing Save on the options page makes this
85
+ browser the active one now.
86
+
69
87
  A Firefox user whose setup worked before only needs step 2 again after
70
88
  restarting Firefox: load `manifest.json` from the same folder, and re-enter
71
89
  the port and token if the options page is empty. Do not repeat the rest of
@@ -77,9 +95,12 @@ Console). It must be pasted again after every page reload.
77
95
 
78
96
  ## Reading the plan
79
97
 
80
- Plans are addressed by `planIdentifier`: a plan name (case-insensitive,
81
- partial match) or an exact plan ID. Prefer the narrowest read that answers the
82
- question.
98
+ Plans are addressed by `planIdentifier`: a plan name (case-insensitive; a
99
+ partial match must identify exactly one plan, and an ambiguous one is refused
100
+ with the candidates listed) or an exact plan ID. The same rule covers
101
+ `eventIdentifier` and the `planA`/`planB` names of `projectionlab_diff`, so a
102
+ read and the following write can never target different items. Prefer the
103
+ narrowest read that answers the question.
83
104
 
84
105
  | Question | Tool |
85
106
  |---|---|
@@ -150,8 +171,8 @@ question.
150
171
  what to use when the user wants to know whether two plans agree without sending
151
172
  their contents to the model.
152
173
  - Results over about 100,000 characters are written to
153
- `~/.projectionlab-mcp/exports/` and returned as a file path. Read the parts
154
- you need from the file.
174
+ `~/.projectionlab-mcp/exports/` and returned as a file path (pruned
175
+ automatically after 30 days). Read the parts you need from the file.
155
176
  - `projectionlab_describe_plugin_api` lists the Plugin API methods the running
156
177
  app exposes. Use it before concluding that a capability does not exist.
157
178
 
@@ -227,6 +248,9 @@ Change the plan only when the user explicitly asks for that change.
227
248
  - Never replace a whole section when a targeted tool can make the change.
228
249
  `projectionlab_update_plan_section` overwrites the complete array, so any
229
250
  event missing from what you send is gone.
251
+ - Never write payloads carrying `[REDACTED]` under secret fields. Reads scrub
252
+ secrets with this placeholder, and write tools refuse payloads containing it
253
+ to prevent overwriting real credentials or keys with placeholder text.
230
254
 
231
255
  **Writes are off by default.** `writesEnabled` in the connection check shows
232
256
  the current mode. While it is `false`, every write tool, dry runs included,
@@ -276,12 +300,18 @@ values intact. Paths must already exist and keep their JSON type; get them from
276
300
  the matching read tool (`get_plan_settings`, `get_plan_section` with
277
301
  `listPaths: true`, or `get_current_finances` with `listPaths: true`). The
278
302
  older `patch` (events) and `data` (accounts) inputs merge top-level fields
279
- only; supply exactly one of the patch inputs.
303
+ only; existing fields keep their JSON type and are validated like
304
+ `pathChanges`, and new fields must be plain, non-secret names. Supply
305
+ exactly one of the patch inputs.
280
306
 
281
307
  Milestones: ProjectionLab always keeps a Retirement and a Life Expectancy
282
308
  milestone and disables deleting them in the app, so never remove either one.
283
309
  `projectionlab_update_plan_section` replaces the whole `milestones` array, so
284
- send both of them back in it.
310
+ send both of them back in it. Its `sectionData` must also match the section's
311
+ current container shape - an array for milestones, an object with an `events`
312
+ array for event sections, an object for `variables`, `montecarlo` and
313
+ `withdrawalStrategy` - or it is refused with `INVALID_INPUT`, dry runs
314
+ included.
285
315
 
286
316
  To explore a what-if without touching the original, clone the plan first and
287
317
  patch the copy.
@@ -294,6 +324,13 @@ refuses the last remaining plan. Its snapshot lets
294
324
  `projectionlab_restore_progress` and `projectionlab_restore_settings` replace
295
325
  an entire data area. Use them only when the user wants exactly that.
296
326
 
327
+ Reads hide secret values: a secret-named field reads back as `[REDACTED]`. A
328
+ `sectionData` or `restore_*` payload that still carries that sentinel under a
329
+ secret-named key is refused with `INVALID_INPUT`, dry runs included, naming the
330
+ paths. Type the real value or change single fields with
331
+ `projectionlab_update_plan_event` or `projectionlab_update_plan_settings`,
332
+ which patch by path and never round-trip hidden values.
333
+
297
334
  ### Change workflow
298
335
 
299
336
  Copy this checklist and work through it for each change:
@@ -365,8 +402,10 @@ Retire at 55 plan."
365
402
  2. `projectionlab_update_plan_event` with
366
403
  `{"planIdentifier": "Retire at 55", "section": "expenses", "eventIdentifier": "Groceries", "pathChanges": {"amount": 600}}`
367
404
  returns `dryRun: true`, the `planName`, `section`, the `event` label, and
368
- `changes` such as `{"amount": {"before": 500, "after": 600}}`. Use
369
- `pathChanges`, not `patch`, so sibling fields are kept. Show the before and
405
+ `changes` such as `{"amount": {"before": 500, "after": 600}}`; the `patch`
406
+ input reports the same before/after objects, and a field it adds reports
407
+ `"before": "[ABSENT]"`. Use `pathChanges`, not `patch`, so sibling fields
408
+ are kept. Show the before and
370
409
  after values to the user in plain words: "In Retire at 55, Groceries goes
371
410
  from $500 to $600 a month. Want me to make that change?"
372
411
  3. After the user approves, repeat the call with `"confirm": true`. The reply
@@ -448,9 +487,10 @@ at 60."
448
487
  and picks the same snapshot, so its field-level list of what differs from the
449
488
  live data shows exactly what that rollback would undo. Show it with the
450
489
  preview when the user asks what will come back.
451
- 4. After the user approves, repeat with `{"nBack": 1, "confirm": true}`. A
452
- confirmed rollback saves the current state first, so another `nBack: 1`
453
- right after a rollback undoes that rollback.
490
+ 4. After the user approves, repeat with `{"nBack": 1, "confirm": true}`. The
491
+ reply reports `verified: true` (or lists `unverifiedSections` if the
492
+ re-read differed). A confirmed rollback saves the current state first, so
493
+ another `nBack: 1` right after a rollback undoes that rollback.
454
494
  5. `snapshotId` names one id from the list. `beforeTime` takes an ISO 8601
455
495
  time carrying a `Z` or `±HH:MM` offset, such as `2026-11-08T15:30:00Z`,
456
496
  and restores the latest snapshot strictly before it; a time with no