@useclickly/mcp-server 1.0.3 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clickly contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.cjs CHANGED
@@ -1,42 +1,24 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
1
+ 'use strict';
24
2
 
25
- // src/cli.ts
26
- var import_node_fs2 = __toESM(require("node:fs"), 1);
27
- var import_node_net = __toESM(require("node:net"), 1);
28
- var import_node_os2 = __toESM(require("node:os"), 1);
29
- var import_node_path2 = __toESM(require("node:path"), 1);
3
+ var fs2 = require('fs');
4
+ var net = require('net');
5
+ var os2 = require('os');
6
+ var path2 = require('path');
7
+ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
8
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
9
+ var zod = require('zod');
10
+ var http = require('http');
30
11
 
31
- // src/server.ts
32
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
33
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
34
- var import_zod = require("zod");
12
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
35
14
 
36
- // src/store.ts
37
- var import_node_fs = __toESM(require("node:fs"), 1);
38
- var import_node_os = __toESM(require("node:os"), 1);
39
- var import_node_path = __toESM(require("node:path"), 1);
15
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
16
+ var net__default = /*#__PURE__*/_interopDefault(net);
17
+ var os2__default = /*#__PURE__*/_interopDefault(os2);
18
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
19
+ var http__default = /*#__PURE__*/_interopDefault(http);
20
+
21
+ // src/cli.ts
40
22
  var AnnotationStore = class {
41
23
  sessions = /* @__PURE__ */ new Map();
42
24
  /** sessionId → (annotationId → AnnotationRecord) */
@@ -45,7 +27,7 @@ var AnnotationStore = class {
45
27
  listeners = /* @__PURE__ */ new Map();
46
28
  storePath;
47
29
  constructor(options = {}) {
48
- this.storePath = options.storePath ?? import_node_path.default.join(import_node_os.default.homedir(), ".clickly", "sessions.json");
30
+ this.storePath = options.storePath ?? path2__default.default.join(os2__default.default.homedir(), ".clickly", "sessions.json");
49
31
  this._load();
50
32
  }
51
33
  /* ── Sessions ─────────────────────────────────────────────────────── */
@@ -112,20 +94,20 @@ var AnnotationStore = class {
112
94
  /* ── Persistence ──────────────────────────────────────────────────── */
113
95
  _persist() {
114
96
  try {
115
- const dir = import_node_path.default.dirname(this.storePath);
116
- if (!import_node_fs.default.existsSync(dir)) import_node_fs.default.mkdirSync(dir, { recursive: true });
97
+ const dir = path2__default.default.dirname(this.storePath);
98
+ if (!fs2__default.default.existsSync(dir)) fs2__default.default.mkdirSync(dir, { recursive: true });
117
99
  const data = {
118
100
  sessions: [...this.sessions.values()],
119
101
  annotations: [...this.annotations.values()].flatMap((m) => [...m.values()])
120
102
  };
121
- import_node_fs.default.writeFileSync(this.storePath, JSON.stringify(data, null, 2), "utf-8");
103
+ fs2__default.default.writeFileSync(this.storePath, JSON.stringify(data, null, 2), "utf-8");
122
104
  } catch {
123
105
  }
124
106
  }
125
107
  _load() {
126
108
  try {
127
- if (!import_node_fs.default.existsSync(this.storePath)) return;
128
- const raw = import_node_fs.default.readFileSync(this.storePath, "utf-8");
109
+ if (!fs2__default.default.existsSync(this.storePath)) return;
110
+ const raw = fs2__default.default.readFileSync(this.storePath, "utf-8");
129
111
  const data = JSON.parse(raw);
130
112
  for (const session of data.sessions ?? []) {
131
113
  this.sessions.set(session.id, session);
@@ -138,9 +120,6 @@ var AnnotationStore = class {
138
120
  }
139
121
  }
140
122
  };
141
-
142
- // src/http-bridge.ts
143
- var import_node_http = __toESM(require("node:http"), 1);
144
123
  var VERSION = "1.0.0";
145
124
  function cors(res) {
146
125
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -182,7 +161,7 @@ function route(req) {
182
161
  return { method: req.method?.toUpperCase() ?? "GET", segments };
183
162
  }
184
163
  function createHttpBridge(store, port) {
185
- const server = import_node_http.default.createServer(async (req, res) => {
164
+ const server = http__default.default.createServer(async (req, res) => {
186
165
  if (req.method === "OPTIONS") {
187
166
  cors(res);
188
167
  res.writeHead(204);
@@ -272,11 +251,11 @@ data: ${payload}
272
251
 
273
252
  // src/server.ts
274
253
  var SessionIdSchema = {
275
- sessionId: import_zod.z.string().describe("Session ID returned by the browser bridge")
254
+ sessionId: zod.z.string().describe("Session ID returned by the browser bridge")
276
255
  };
277
256
  var AnnotationIdSchema = {
278
257
  ...SessionIdSchema,
279
- annotationId: import_zod.z.string().describe("Annotation ID")
258
+ annotationId: zod.z.string().describe("Annotation ID")
280
259
  };
281
260
  function errResult(message) {
282
261
  return {
@@ -298,7 +277,7 @@ async function createServer(options = {}) {
298
277
  const port = options.port ?? 4747;
299
278
  const store = options.store ?? new AnnotationStore({ storePath: options.storePath });
300
279
  const bridge = createHttpBridge(store, port);
301
- const mcp = new import_mcp.McpServer(
280
+ const mcp = new mcp_js.McpServer(
302
281
  { name: "clickly", version: "1.0.0" },
303
282
  {
304
283
  capabilities: { tools: {} },
@@ -334,6 +313,30 @@ async function createServer(options = {}) {
334
313
  return okResult(store.listAnnotations(sessionId));
335
314
  }
336
315
  );
316
+ mcp.registerTool(
317
+ "clickly_list_layout_changes",
318
+ {
319
+ description: "List Layout Mode annotations only \u2014 placements (new components the developer wants added) and rearranges (existing on-page sections they want moved). Use this when you want to focus on structural / design changes without scrolling past feedback annotations. Returns the same AFS 1.1 records as clickly_list_annotations, filtered by `kind`.",
320
+ inputSchema: {
321
+ ...SessionIdSchema,
322
+ kind: zod.z.enum(["placement", "rearrange", "both"]).optional().describe("Filter to just placements, just rearranges, or both (default)"),
323
+ status: zod.z.enum(["pending", "acknowledged", "resolved", "dismissed"]).optional().describe("Optional status filter \u2014 useful for ignoring resolved layouts")
324
+ }
325
+ },
326
+ async ({ sessionId, kind = "both", status }) => {
327
+ const session = store.getSession(sessionId);
328
+ if (!session) return errResult(`Session not found: ${sessionId}`);
329
+ const all = store.listAnnotations(sessionId);
330
+ const filtered = all.filter((a) => {
331
+ const isLayout = a.kind === "placement" || a.kind === "rearrange";
332
+ if (!isLayout) return false;
333
+ if (kind !== "both" && a.kind !== kind) return false;
334
+ if (status && a.status !== status) return false;
335
+ return true;
336
+ });
337
+ return okResult(filtered);
338
+ }
339
+ );
337
340
  mcp.registerTool(
338
341
  "clickly_get_annotation",
339
342
  {
@@ -375,7 +378,7 @@ async function createServer(options = {}) {
375
378
  description: "Mark an annotation as 'resolved' \u2014 the issue has been fixed. Optionally include a resolution note. The pin will show as resolved in the developer's browser.",
376
379
  inputSchema: {
377
380
  ...AnnotationIdSchema,
378
- note: import_zod.z.string().optional().describe("Optional resolution note to append to the thread")
381
+ note: zod.z.string().optional().describe("Optional resolution note to append to the thread")
379
382
  }
380
383
  },
381
384
  async ({ sessionId, annotationId, note }) => {
@@ -428,7 +431,7 @@ async function createServer(options = {}) {
428
431
  description: "Append a thread message to an annotation. Use this to ask a clarifying question, describe what you changed, or communicate back to the developer who left feedback.",
429
432
  inputSchema: {
430
433
  ...AnnotationIdSchema,
431
- message: import_zod.z.string().describe("Your reply message")
434
+ message: zod.z.string().describe("Your reply message")
432
435
  }
433
436
  },
434
437
  async ({ sessionId, annotationId, message }) => {
@@ -451,7 +454,7 @@ async function createServer(options = {}) {
451
454
  }
452
455
  }
453
456
  );
454
- const transport = new import_stdio.StdioServerTransport();
457
+ const transport = new stdio_js.StdioServerTransport();
455
458
  await mcp.connect(transport);
456
459
  return {
457
460
  port,
@@ -463,7 +466,6 @@ async function createServer(options = {}) {
463
466
  }
464
467
 
465
468
  // src/cli.ts
466
- var import_meta = {};
467
469
  var [, , cmd, ...args] = process.argv;
468
470
  function log(msg) {
469
471
  process.stderr.write(msg + "\n");
@@ -477,7 +479,7 @@ function parseFlag(flag, fallback) {
477
479
  }
478
480
  function isPortFree(port) {
479
481
  return new Promise((resolve) => {
480
- const srv = import_node_net.default.createServer();
482
+ const srv = net__default.default.createServer();
481
483
  srv.once("error", () => resolve(false));
482
484
  srv.once("listening", () => srv.close(() => resolve(true)));
483
485
  srv.listen(port, "127.0.0.1");
@@ -485,7 +487,7 @@ function isPortFree(port) {
485
487
  }
486
488
  async function cmdServer() {
487
489
  const port = parseInt(parseFlag("--port", "4747"), 10);
488
- const storePath = parseFlag("--store", import_node_path2.default.join(import_node_os2.default.homedir(), ".clickly", "sessions.json"));
490
+ const storePath = parseFlag("--store", path2__default.default.join(os2__default.default.homedir(), ".clickly", "sessions.json"));
489
491
  log(`[clickly-mcp] Starting MCP server (stdio) + HTTP bridge on :${port}`);
490
492
  log(`[clickly-mcp] Persistence: ${storePath}`);
491
493
  log(`[clickly-mcp] Ready. Connect your AI agent via stdio.`);
@@ -503,7 +505,7 @@ async function cmdServer() {
503
505
  async function cmdDoctor() {
504
506
  let allOk = true;
505
507
  const port = parseInt(parseFlag("--port", "4747"), 10);
506
- const storePath = import_node_path2.default.join(import_node_os2.default.homedir(), ".clickly", "sessions.json");
508
+ const storePath = path2__default.default.join(os2__default.default.homedir(), ".clickly", "sessions.json");
507
509
  out("clickly-mcp doctor\n");
508
510
  const nodeVersion = process.versions.node;
509
511
  const major = parseInt(nodeVersion.split(".")[0] ?? "0", 10);
@@ -513,22 +515,22 @@ async function cmdDoctor() {
513
515
  const portFree = await isPortFree(port);
514
516
  out(` Port ${port}: ${portFree ? "available \u2713" : "IN USE \u2717 (another process is running on this port)"}`);
515
517
  if (!portFree) allOk = false;
516
- const storeDir = import_node_path2.default.dirname(storePath);
518
+ const storeDir = path2__default.default.dirname(storePath);
517
519
  let storeOk = false;
518
520
  try {
519
- import_node_fs2.default.mkdirSync(storeDir, { recursive: true });
520
- const testFile = import_node_path2.default.join(storeDir, ".write-test");
521
- import_node_fs2.default.writeFileSync(testFile, "ok");
522
- import_node_fs2.default.unlinkSync(testFile);
521
+ fs2__default.default.mkdirSync(storeDir, { recursive: true });
522
+ const testFile = path2__default.default.join(storeDir, ".write-test");
523
+ fs2__default.default.writeFileSync(testFile, "ok");
524
+ fs2__default.default.unlinkSync(testFile);
523
525
  storeOk = true;
524
526
  } catch {
525
527
  storeOk = false;
526
528
  }
527
529
  out(` Store dir (${storeDir}): ${storeOk ? "writable \u2713" : "NOT writable \u2717"}`);
528
530
  if (!storeOk) allOk = false;
529
- if (import_node_fs2.default.existsSync(storePath)) {
531
+ if (fs2__default.default.existsSync(storePath)) {
530
532
  try {
531
- const data = JSON.parse(import_node_fs2.default.readFileSync(storePath, "utf-8"));
533
+ const data = JSON.parse(fs2__default.default.readFileSync(storePath, "utf-8"));
532
534
  const count = (data.sessions ?? []).length;
533
535
  out(` Persisted sessions: ${count}`);
534
536
  } catch {
@@ -544,7 +546,7 @@ async function cmdDoctor() {
544
546
  function cmdInit() {
545
547
  const write = args.includes("--write");
546
548
  const binPath = process.execPath;
547
- const mcpBin = new URL("../dist/cli.js", import_meta.url).pathname;
549
+ const mcpBin = new URL("../dist/cli.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))).pathname;
548
550
  const claudeSnippet = JSON.stringify(
549
551
  {
550
552
  mcpServers: {
@@ -582,18 +584,18 @@ function cmdInit() {
582
584
  out(`(node binary: ${binPath})`);
583
585
  return;
584
586
  }
585
- const claudeConfigDir = import_node_path2.default.join(import_node_os2.default.homedir(), ".claude");
586
- const claudeConfigPath = import_node_path2.default.join(claudeConfigDir, "claude_desktop_config.json");
587
+ const claudeConfigDir = path2__default.default.join(os2__default.default.homedir(), ".claude");
588
+ const claudeConfigPath = path2__default.default.join(claudeConfigDir, "claude_desktop_config.json");
587
589
  try {
588
- import_node_fs2.default.mkdirSync(claudeConfigDir, { recursive: true });
590
+ fs2__default.default.mkdirSync(claudeConfigDir, { recursive: true });
589
591
  let existing = {};
590
- if (import_node_fs2.default.existsSync(claudeConfigPath)) {
591
- existing = JSON.parse(import_node_fs2.default.readFileSync(claudeConfigPath, "utf-8"));
592
+ if (fs2__default.default.existsSync(claudeConfigPath)) {
593
+ existing = JSON.parse(fs2__default.default.readFileSync(claudeConfigPath, "utf-8"));
592
594
  }
593
595
  const mcpServers = existing.mcpServers ?? {};
594
596
  mcpServers.clickly = { command: "node", args: [mcpBin, "server"], env: {} };
595
597
  existing.mcpServers = mcpServers;
596
- import_node_fs2.default.writeFileSync(claudeConfigPath, JSON.stringify(existing, null, 2), "utf-8");
598
+ fs2__default.default.writeFileSync(claudeConfigPath, JSON.stringify(existing, null, 2), "utf-8");
597
599
  out(`\u2713 Written to ${claudeConfigPath}`);
598
600
  out(" Restart Claude Code (or your agent) to pick up the new MCP server.");
599
601
  } catch (err) {
@@ -630,3 +632,4 @@ switch (cmd) {
630
632
  process.exit(cmd ? 1 : 0);
631
633
  }
632
634
  //# sourceMappingURL=cli.cjs.map
635
+ //# sourceMappingURL=cli.cjs.map
package/dist/cli.cjs.map CHANGED
@@ -1,7 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/cli.ts", "../src/server.ts", "../src/store.ts", "../src/http-bridge.ts"],
4
- "sourcesContent": ["/**\n * `clickly-mcp` CLI\n *\n * Subcommands:\n * server Start the MCP stdio server + HTTP bridge on :4747\n * doctor Check environment: Node version, port availability, store writability\n * init Print (or write) the claude_desktop_config.json / .cursor/mcp.json snippet\n */\n\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { createServer } from \"./server.js\";\n\nconst [, , cmd, ...args] = process.argv;\n\n/* \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction log(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nfunction out(msg: string): void {\n process.stdout.write(msg + \"\\n\");\n}\n\nfunction parseFlag(flag: string, fallback: string): string {\n const idx = args.indexOf(flag);\n return idx !== -1 && args[idx + 1] != null ? (args[idx + 1] as string) : fallback;\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = net.createServer();\n srv.once(\"error\", () => resolve(false));\n srv.once(\"listening\", () => srv.close(() => resolve(true)));\n srv.listen(port, \"127.0.0.1\");\n });\n}\n\n/* \u2500\u2500\u2500 server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nasync function cmdServer(): Promise<void> {\n const port = parseInt(parseFlag(\"--port\", \"4747\"), 10);\n const storePath = parseFlag(\"--store\", path.join(os.homedir(), \".clickly\", \"sessions.json\"));\n\n log(`[clickly-mcp] Starting MCP server (stdio) + HTTP bridge on :${port}`);\n log(`[clickly-mcp] Persistence: ${storePath}`);\n log(`[clickly-mcp] Ready. Connect your AI agent via stdio.`);\n\n const handle = await createServer({ port, storePath });\n\n process.on(\"SIGINT\", async () => {\n log(\"\\n[clickly-mcp] Shutting down\u2026\");\n await handle.close();\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", async () => {\n await handle.close();\n process.exit(0);\n });\n}\n\n/* \u2500\u2500\u2500 doctor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nasync function cmdDoctor(): Promise<void> {\n let allOk = true;\n const port = parseInt(parseFlag(\"--port\", \"4747\"), 10);\n const storePath = path.join(os.homedir(), \".clickly\", \"sessions.json\");\n\n out(\"clickly-mcp doctor\\n\");\n\n // Node version\n const nodeVersion = process.versions.node;\n const major = parseInt(nodeVersion.split(\".\")[0] ?? \"0\", 10);\n const nodeOk = major >= 18;\n out(` Node.js: ${nodeVersion} ${nodeOk ? \"\u2713\" : \"\u2717 (need \u2265 18)\"}`);\n if (!nodeOk) allOk = false;\n\n // Port availability\n const portFree = await isPortFree(port);\n out(` Port ${port}: ${portFree ? \"available \u2713\" : \"IN USE \u2717 (another process is running on this port)\"}`);\n if (!portFree) allOk = false;\n\n // Store directory writability\n const storeDir = path.dirname(storePath);\n let storeOk = false;\n try {\n fs.mkdirSync(storeDir, { recursive: true });\n const testFile = path.join(storeDir, \".write-test\");\n fs.writeFileSync(testFile, \"ok\");\n fs.unlinkSync(testFile);\n storeOk = true;\n } catch {\n storeOk = false;\n }\n out(` Store dir (${storeDir}): ${storeOk ? \"writable \u2713\" : \"NOT writable \u2717\"}`);\n if (!storeOk) allOk = false;\n\n // Existing sessions\n if (fs.existsSync(storePath)) {\n try {\n const data = JSON.parse(fs.readFileSync(storePath, \"utf-8\"));\n const count = (data.sessions ?? []).length;\n out(` Persisted sessions: ${count}`);\n } catch {\n out(` Persisted sessions: (could not parse ${storePath})`);\n }\n } else {\n out(` Persisted sessions: none yet (store will be created on first use)`);\n }\n\n out(`\\n ${allOk ? \"All checks passed \u2713\" : \"Some checks failed \u2717 \u2014 fix the issues above before starting\"}`);\n process.exit(allOk ? 0 : 1);\n}\n\n/* \u2500\u2500\u2500 init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction cmdInit(): void {\n const write = args.includes(\"--write\");\n const binPath = process.execPath; // node\n const mcpBin = new URL(\"../dist/cli.js\", import.meta.url).pathname;\n\n // Claude Code / claude_desktop_config.json snippet\n const claudeSnippet = JSON.stringify(\n {\n mcpServers: {\n clickly: {\n command: \"node\",\n args: [mcpBin, \"server\"],\n env: {},\n },\n },\n },\n null,\n 2,\n );\n\n // Cursor .cursor/mcp.json snippet\n const cursorSnippet = JSON.stringify(\n {\n mcpServers: {\n clickly: {\n command: \"node\",\n args: [mcpBin, \"server\"],\n },\n },\n },\n null,\n 2,\n );\n\n if (!write) {\n out(\"# Clickly MCP \u2014 setup snippets\\n\");\n out(\"## Claude Code (~/.claude.json or claude_desktop_config.json)\\n\");\n out(claudeSnippet);\n out(\"\\n## Cursor (.cursor/mcp.json)\\n\");\n out(cursorSnippet);\n out(\n \"\\nRun `clickly-mcp init --write` to automatically write the Claude Code config.\\n\",\n );\n out(`(node binary: ${binPath})`);\n return;\n }\n\n // --write: merge into Claude Code config\n const claudeConfigDir = path.join(os.homedir(), \".claude\");\n const claudeConfigPath = path.join(claudeConfigDir, \"claude_desktop_config.json\");\n\n try {\n fs.mkdirSync(claudeConfigDir, { recursive: true });\n\n let existing: Record<string, unknown> = {};\n if (fs.existsSync(claudeConfigPath)) {\n existing = JSON.parse(fs.readFileSync(claudeConfigPath, \"utf-8\"));\n }\n\n const mcpServers = (existing.mcpServers as Record<string, unknown>) ?? {};\n mcpServers.clickly = { command: \"node\", args: [mcpBin, \"server\"], env: {} };\n existing.mcpServers = mcpServers;\n\n fs.writeFileSync(claudeConfigPath, JSON.stringify(existing, null, 2), \"utf-8\");\n out(`\u2713 Written to ${claudeConfigPath}`);\n out(\" Restart Claude Code (or your agent) to pick up the new MCP server.\");\n } catch (err) {\n out(`\u2717 Could not write config: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\n/* \u2500\u2500\u2500 Dispatch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nswitch (cmd) {\n case \"server\":\n cmdServer().catch((err) => {\n log(`[clickly-mcp] Fatal: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n });\n break;\n\n case \"doctor\":\n cmdDoctor().catch((err) => {\n log(`doctor error: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n });\n break;\n\n case \"init\":\n cmdInit();\n break;\n\n default:\n out(\"clickly-mcp \u2014 MCP server for the Clickly annotation toolbar\\n\");\n out(\"Usage: clickly-mcp <command> [options]\\n\");\n out(\"Commands:\");\n out(\" server Start the MCP stdio server (+ HTTP bridge on :4747)\");\n out(\" Options: --port <n> HTTP bridge port (default: 4747)\");\n out(\" --store <path> Persistence file (default: ~/.clickly/sessions.json)\");\n out(\" doctor Check environment (Node version, port, store writability)\");\n out(\" init Print config snippets for Claude Code / Cursor\");\n out(\" Options: --write Write directly to ~/.claude/claude_desktop_config.json\");\n process.exit(cmd ? 1 : 0);\n}\n", "/**\n * @useclickly/mcp-server \u2014 real implementation.\n *\n * Exposes a Model Context Protocol stdio server backed by an in-process\n * HTTP bridge. AI coding agents connect via stdio; the browser component\n * connects via HTTP on localhost:4747.\n *\n * MCP tools\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * clickly_list_sessions List all annotation sessions\n * clickly_list_annotations List annotations for a session\n * clickly_get_annotation Fetch a single annotation by ID\n * clickly_acknowledge Mark annotation as acknowledged\n * clickly_resolve Mark annotation as resolved\n * clickly_dismiss Mark annotation as dismissed\n * clickly_reply Append a thread message to an annotation\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { AnnotationStore } from \"./store.js\";\nimport { createHttpBridge } from \"./http-bridge.js\";\n\nexport { AnnotationStore } from \"./store.js\";\n\n/* \u2500\u2500\u2500 Public types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface ServerOptions {\n /** HTTP port for the browser bridge. Default: 4747. */\n port?: number;\n /** On-disk path for session persistence. Default: ~/.clickly/sessions.json. */\n storePath?: string;\n /** Custom store instance (useful for testing). */\n store?: AnnotationStore;\n}\n\nexport interface ServerHandle {\n port: number;\n close(): Promise<void>;\n}\n\n/* \u2500\u2500\u2500 Shared input schemas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nconst SessionIdSchema = {\n sessionId: z.string().describe(\"Session ID returned by the browser bridge\"),\n};\n\nconst AnnotationIdSchema = {\n ...SessionIdSchema,\n annotationId: z.string().describe(\"Annotation ID\"),\n};\n\n/* \u2500\u2500\u2500 Helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction errResult(message: string) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: message }],\n };\n}\n\nfunction okResult(data: unknown) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof data === \"string\" ? data : JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n\n/* \u2500\u2500\u2500 createServer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport async function createServer(options: ServerOptions = {}): Promise<ServerHandle> {\n const port = options.port ?? 4747;\n const store =\n options.store ?? new AnnotationStore({ storePath: options.storePath });\n\n /* \u2500\u2500 HTTP bridge (browser \u2192 server) \u2500\u2500\u2500 */\n const bridge = createHttpBridge(store, port);\n\n /* \u2500\u2500 MCP server (stdio, agent \u2192 server) \u2500\u2500\u2500 */\n const mcp = new McpServer(\n { name: \"clickly\", version: \"1.0.0\" },\n {\n capabilities: { tools: {} },\n instructions:\n \"Clickly exposes UI annotations captured in a running React development app. \" +\n \"Use these tools to read feedback, acknowledge issues, resolve them, or reply \" +\n \"to start a conversation thread with the developer.\",\n },\n );\n\n /* \u2500\u2500 clickly_list_sessions \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_list_sessions\",\n {\n description:\n \"List all Clickly annotation sessions. Each session corresponds to a page/URL \" +\n \"where the developer opened the Clickly toolbar. Returns session IDs, URLs, \" +\n \"creation timestamps, and annotation counts.\",\n inputSchema: {},\n },\n async () => {\n const sessions = store.listSessions();\n const rows = sessions.map((s) => ({\n sessionId: s.id,\n url: s.url,\n createdAt: new Date(s.createdAt).toISOString(),\n annotationCount: store.listAnnotations(s.id).length,\n }));\n return okResult(rows);\n },\n );\n\n /* \u2500\u2500 clickly_list_annotations \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_list_annotations\",\n {\n description:\n \"List all annotations in a session. Includes element path, position, React \" +\n \"component tree, source file/line, feedback comment, and lifecycle status.\",\n inputSchema: SessionIdSchema,\n },\n async ({ sessionId }) => {\n const session = store.getSession(sessionId);\n if (!session) return errResult(`Session not found: ${sessionId}`);\n return okResult(store.listAnnotations(sessionId));\n },\n );\n\n /* \u2500\u2500 clickly_get_annotation \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_get_annotation\",\n {\n description:\n \"Fetch a single annotation by ID. Returns the full AFS 1.1 record including \" +\n \"element metadata, bounding box, React component chain, source location, \" +\n \"and any thread messages.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n const annotation = store.getAnnotation(sessionId, annotationId);\n if (!annotation) {\n return errResult(\n `Annotation not found: ${annotationId} in session ${sessionId}`,\n );\n }\n return okResult(annotation);\n },\n );\n\n /* \u2500\u2500 clickly_acknowledge \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_acknowledge\",\n {\n description:\n \"Mark an annotation as 'acknowledged' \u2014 you have seen it and are working on it. \" +\n \"The developer's UI will show an acknowledged badge on the annotation pin.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n try {\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"acknowledged\",\n });\n return okResult(\n `Acknowledged annotation ${updated.id} in session ${sessionId}.`,\n );\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* \u2500\u2500 clickly_resolve \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_resolve\",\n {\n description:\n \"Mark an annotation as 'resolved' \u2014 the issue has been fixed. Optionally include \" +\n \"a resolution note. The pin will show as resolved in the developer's browser.\",\n inputSchema: {\n ...AnnotationIdSchema,\n note: z\n .string()\n .optional()\n .describe(\"Optional resolution note to append to the thread\"),\n },\n },\n async ({ sessionId, annotationId, note }) => {\n try {\n const existing = store.getAnnotation(sessionId, annotationId);\n if (!existing) return errResult(`Annotation not found: ${annotationId}`);\n\n const thread = existing.thread ? [...existing.thread] : [];\n if (note) {\n thread.push({\n id: crypto.randomUUID(),\n role: \"agent\",\n content: note,\n timestamp: Date.now(),\n });\n }\n\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"resolved\",\n resolvedAt: new Date().toISOString(),\n resolvedBy: \"agent\",\n ...(note ? { thread } : {}),\n });\n return okResult(\n `Resolved annotation ${updated.id}.${note ? ` Note: \"${note}\"` : \"\"}`,\n );\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* \u2500\u2500 clickly_dismiss \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_dismiss\",\n {\n description:\n \"Mark an annotation as 'dismissed' \u2014 it is not actionable or is a duplicate. \" +\n \"Dismissed annotations are hidden from the default list view.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n try {\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"dismissed\",\n });\n return okResult(`Dismissed annotation ${updated.id}.`);\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* \u2500\u2500 clickly_reply \u2500\u2500\u2500 */\n mcp.registerTool(\n \"clickly_reply\",\n {\n description:\n \"Append a thread message to an annotation. Use this to ask a clarifying question, \" +\n \"describe what you changed, or communicate back to the developer who left feedback.\",\n inputSchema: {\n ...AnnotationIdSchema,\n message: z.string().describe(\"Your reply message\"),\n },\n },\n async ({ sessionId, annotationId, message }) => {\n try {\n const existing = store.getAnnotation(sessionId, annotationId);\n if (!existing) return errResult(`Annotation not found: ${annotationId}`);\n\n const thread = [\n ...(existing.thread ?? []),\n {\n id: crypto.randomUUID(),\n role: \"agent\" as const,\n content: message,\n timestamp: Date.now(),\n },\n ];\n\n store.updateAnnotation(sessionId, annotationId, { thread });\n return okResult(`Reply added to annotation ${annotationId}.`);\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* \u2500\u2500 Connect stdio transport \u2500\u2500\u2500 */\n const transport = new StdioServerTransport();\n await mcp.connect(transport);\n\n return {\n port,\n close: async () => {\n await mcp.close();\n await bridge.close();\n },\n };\n}\n", "/**\n * Annotation + Session store.\n *\n * Holds everything in-memory for fast reads.\n * Persists to a JSON file on every mutating operation so the MCP client\n * can survive a server restart without losing annotations.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { Annotation } from \"@useclickly/core\";\n\n/* \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface Session {\n id: string;\n url: string;\n createdAt: number;\n /** Monotonically increasing counter \u2014 incremented on every annotation mutation. */\n seq: number;\n}\n\nexport interface AnnotationRecord extends Annotation {\n sessionId: string;\n}\n\nexport interface StoreOptions {\n /**\n * Where to persist sessions + annotations.\n * Defaults to `~/.clickly/sessions.json`.\n */\n storePath?: string;\n}\n\n/* \u2500\u2500\u2500 Persisted shape \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\ninterface PersistedData {\n sessions: Session[];\n annotations: AnnotationRecord[];\n}\n\n/* \u2500\u2500\u2500 Store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport class AnnotationStore {\n private sessions = new Map<string, Session>();\n /** sessionId \u2192 (annotationId \u2192 AnnotationRecord) */\n private annotations = new Map<string, Map<string, AnnotationRecord>>();\n /** Listeners waiting for new annotations in a session (SSE bridge) */\n private listeners = new Map<string, Set<(a: AnnotationRecord, seq: number) => void>>();\n\n readonly storePath: string;\n\n constructor(options: StoreOptions = {}) {\n this.storePath =\n options.storePath ?? path.join(os.homedir(), \".clickly\", \"sessions.json\");\n this._load();\n }\n\n /* \u2500\u2500 Sessions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n createSession(url: string): Session {\n const id = crypto.randomUUID();\n const session: Session = { id, url, createdAt: Date.now(), seq: 0 };\n this.sessions.set(id, session);\n this.annotations.set(id, new Map());\n this._persist();\n return session;\n }\n\n getSession(sessionId: string): Session | undefined {\n return this.sessions.get(sessionId);\n }\n\n listSessions(): Session[] {\n return [...this.sessions.values()].sort((a, b) => b.createdAt - a.createdAt);\n }\n\n /* \u2500\u2500 Annotations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n addAnnotation(sessionId: string, annotation: Annotation): AnnotationRecord {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n const bucket = this.annotations.get(sessionId)!;\n const record: AnnotationRecord = { ...annotation, sessionId };\n bucket.set(annotation.id, record);\n\n session.seq += 1;\n const seq = session.seq;\n\n this._persist();\n this._emit(sessionId, record, seq);\n return record;\n }\n\n getAnnotation(sessionId: string, annotationId: string): AnnotationRecord | undefined {\n return this.annotations.get(sessionId)?.get(annotationId);\n }\n\n listAnnotations(sessionId: string): AnnotationRecord[] {\n const bucket = this.annotations.get(sessionId);\n if (!bucket) return [];\n return [...bucket.values()].sort((a, b) => a.timestamp - b.timestamp);\n }\n\n updateAnnotation(\n sessionId: string,\n annotationId: string,\n patch: Partial<Annotation>,\n ): AnnotationRecord {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n const bucket = this.annotations.get(sessionId);\n const existing = bucket?.get(annotationId);\n if (!existing) throw new Error(`Annotation not found: ${annotationId}`);\n\n const updated: AnnotationRecord = { ...existing, ...patch, sessionId };\n bucket!.set(annotationId, updated);\n\n session.seq += 1;\n const seq = session.seq;\n\n this._persist();\n this._emit(sessionId, updated, seq);\n return updated;\n }\n\n /* \u2500\u2500 SSE subscriptions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n subscribe(\n sessionId: string,\n cb: (annotation: AnnotationRecord, seq: number) => void,\n ): () => void {\n if (!this.listeners.has(sessionId)) {\n this.listeners.set(sessionId, new Set());\n }\n this.listeners.get(sessionId)!.add(cb);\n return () => this.listeners.get(sessionId)?.delete(cb);\n }\n\n private _emit(sessionId: string, annotation: AnnotationRecord, seq: number): void {\n this.listeners.get(sessionId)?.forEach((cb) => cb(annotation, seq));\n }\n\n /* \u2500\u2500 Persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n private _persist(): void {\n try {\n const dir = path.dirname(this.storePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n\n const data: PersistedData = {\n sessions: [...this.sessions.values()],\n annotations: [...this.annotations.values()].flatMap((m) => [...m.values()]),\n };\n fs.writeFileSync(this.storePath, JSON.stringify(data, null, 2), \"utf-8\");\n } catch {\n // Persistence failures are non-fatal \u2014 data is still in-memory.\n }\n }\n\n private _load(): void {\n try {\n if (!fs.existsSync(this.storePath)) return;\n const raw = fs.readFileSync(this.storePath, \"utf-8\");\n const data: PersistedData = JSON.parse(raw);\n\n for (const session of data.sessions ?? []) {\n this.sessions.set(session.id, session);\n this.annotations.set(session.id, new Map());\n }\n for (const annotation of data.annotations ?? []) {\n this.annotations.get(annotation.sessionId)?.set(annotation.id, annotation);\n }\n } catch {\n // Corrupted or missing file \u2014 start fresh.\n }\n }\n}\n", "/**\n * HTTP bridge \u2014 lets the in-browser <Clickly /> component talk to this server.\n *\n * Endpoints\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * POST /sessions\n * Body: { url: string }\n * Returns: { sessionId: string }\n *\n * POST /sessions/:sessionId/annotations\n * Body: Annotation (AFS 1.1)\n * Returns: { ok: true, id: string }\n *\n * GET /sessions/:sessionId/annotations\n * Returns: Annotation[]\n *\n * GET /sessions/:sessionId/events\n * Server-Sent Events stream \u2014 emits `annotation` events on every add/update.\n * Each event: `data: { annotation, seq }\\n\\n`\n *\n * GET /sessions\n * Returns: Session[]\n *\n * GET /health\n * Returns: { ok: true, version: string }\n */\n\nimport http from \"node:http\";\nimport type { AnnotationStore } from \"./store.js\";\n\nconst VERSION = \"1.0.0\";\n\n/* \u2500\u2500\u2500 CORS helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction cors(res: http.ServerResponse): void {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n}\n\n/* \u2500\u2500\u2500 Response helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction json(res: http.ServerResponse, status: number, body: unknown): void {\n cors(res);\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n });\n res.end(payload);\n}\n\nfunction notFound(res: http.ServerResponse): void {\n json(res, 404, { error: \"not_found\" });\n}\n\nfunction badRequest(res: http.ServerResponse, message: string): void {\n json(res, 400, { error: \"bad_request\", message });\n}\n\n/* \u2500\u2500\u2500 Body reader \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction readBody(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => {\n try {\n resolve(raw ? JSON.parse(raw) : {});\n } catch {\n reject(new Error(\"Invalid JSON body\"));\n }\n });\n req.on(\"error\", reject);\n });\n}\n\n/* \u2500\u2500\u2500 Router \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction route(\n req: http.IncomingMessage,\n): { method: string; segments: string[] } | null {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const segments = url.pathname.replace(/^\\//, \"\").split(\"/\").filter(Boolean);\n return { method: req.method?.toUpperCase() ?? \"GET\", segments };\n}\n\n/* \u2500\u2500\u2500 Bridge factory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface BridgeHandle {\n port: number;\n close(): Promise<void>;\n}\n\nexport function createHttpBridge(store: AnnotationStore, port: number): BridgeHandle {\n const server = http.createServer(async (req, res) => {\n // Preflight\n if (req.method === \"OPTIONS\") {\n cors(res);\n res.writeHead(204);\n res.end();\n return;\n }\n\n const r = route(req);\n if (!r) return notFound(res);\n const { method, segments } = r;\n\n try {\n // GET /health\n if (method === \"GET\" && segments[0] === \"health\") {\n return json(res, 200, { ok: true, version: VERSION });\n }\n\n // GET /sessions\n if (method === \"GET\" && segments.length === 1 && segments[0] === \"sessions\") {\n return json(res, 200, store.listSessions());\n }\n\n // POST /sessions\n if (method === \"POST\" && segments.length === 1 && segments[0] === \"sessions\") {\n const body = (await readBody(req)) as { url?: string };\n if (!body.url) return badRequest(res, \"url is required\");\n const session = store.createSession(body.url);\n return json(res, 201, { sessionId: session.id });\n }\n\n // Routes under /sessions/:sessionId/...\n if (segments[0] === \"sessions\" && segments[1]) {\n const sessionId = segments[1];\n const session = store.getSession(sessionId);\n if (!session) return json(res, 404, { error: \"session_not_found\" });\n\n const sub = segments[2];\n\n // GET /sessions/:id/annotations\n if (method === \"GET\" && sub === \"annotations\" && segments.length === 3) {\n return json(res, 200, store.listAnnotations(sessionId));\n }\n\n // POST /sessions/:id/annotations\n if (method === \"POST\" && sub === \"annotations\" && segments.length === 3) {\n const body = await readBody(req);\n if (!body || typeof body !== \"object\") return badRequest(res, \"body required\");\n const annotation = body as Record<string, unknown>;\n if (!annotation.id || !annotation.comment || !annotation.elementPath) {\n return badRequest(res, \"id, comment, elementPath are required\");\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const record = store.addAnnotation(sessionId, annotation as any);\n return json(res, 201, { ok: true, id: record.id });\n }\n\n // GET /sessions/:id/events \u2014 SSE\n if (method === \"GET\" && sub === \"events\" && segments.length === 3) {\n cors(res);\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\", // disable nginx buffering\n });\n\n // Send current sequence as a \"connected\" event\n res.write(`event: connected\\ndata: ${JSON.stringify({ seq: session.seq })}\\n\\n`);\n\n // Subscribe to future annotations\n const unsubscribe = store.subscribe(sessionId, (annotation, seq) => {\n const payload = JSON.stringify({ annotation, seq });\n res.write(`event: annotation\\ndata: ${payload}\\n\\n`);\n });\n\n // Heartbeat every 15s to keep connection alive through proxies\n const heartbeat = setInterval(() => {\n res.write(`: heartbeat\\n\\n`);\n }, 15_000);\n\n req.on(\"close\", () => {\n clearInterval(heartbeat);\n unsubscribe();\n });\n return; // SSE \u2014 don't close\n }\n }\n\n notFound(res);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"internal_error\";\n json(res, 500, { error: \"internal_error\", message });\n }\n });\n\n server.listen(port);\n\n return {\n port,\n close: () =>\n new Promise((resolve, reject) =>\n server.close((err) => (err ? reject(err) : resolve())),\n ),\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["import_node_fs", "import_node_os", "import_node_path", "path", "os", "fs", "http", "net", "path", "os", "fs"]
7
- }
1
+ {"version":3,"sources":["../src/store.ts","../src/http-bridge.ts","../src/server.ts","../src/cli.ts"],"names":["path","os","fs","http","z","McpServer","StdioServerTransport","net"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * Annotation + Session store.\n *\n * Holds everything in-memory for fast reads.\n * Persists to a JSON file on every mutating operation so the MCP client\n * can survive a server restart without losing annotations.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { Annotation } from \"@useclickly/core\";\n\n/* ─── Types ──────────────────────────────────────────────────────────── */\n\nexport interface Session {\n id: string;\n url: string;\n createdAt: number;\n /** Monotonically increasing counter — incremented on every annotation mutation. */\n seq: number;\n}\n\nexport interface AnnotationRecord extends Annotation {\n sessionId: string;\n}\n\nexport interface StoreOptions {\n /**\n * Where to persist sessions + annotations.\n * Defaults to `~/.clickly/sessions.json`.\n */\n storePath?: string;\n}\n\n/* ─── Persisted shape ────────────────────────────────────────────────── */\n\ninterface PersistedData {\n sessions: Session[];\n annotations: AnnotationRecord[];\n}\n\n/* ─── Store ──────────────────────────────────────────────────────────── */\n\nexport class AnnotationStore {\n private sessions = new Map<string, Session>();\n /** sessionId → (annotationId → AnnotationRecord) */\n private annotations = new Map<string, Map<string, AnnotationRecord>>();\n /** Listeners waiting for new annotations in a session (SSE bridge) */\n private listeners = new Map<string, Set<(a: AnnotationRecord, seq: number) => void>>();\n\n readonly storePath: string;\n\n constructor(options: StoreOptions = {}) {\n this.storePath =\n options.storePath ?? path.join(os.homedir(), \".clickly\", \"sessions.json\");\n this._load();\n }\n\n /* ── Sessions ─────────────────────────────────────────────────────── */\n\n createSession(url: string): Session {\n const id = crypto.randomUUID();\n const session: Session = { id, url, createdAt: Date.now(), seq: 0 };\n this.sessions.set(id, session);\n this.annotations.set(id, new Map());\n this._persist();\n return session;\n }\n\n getSession(sessionId: string): Session | undefined {\n return this.sessions.get(sessionId);\n }\n\n listSessions(): Session[] {\n return [...this.sessions.values()].sort((a, b) => b.createdAt - a.createdAt);\n }\n\n /* ── Annotations ──────────────────────────────────────────────────── */\n\n addAnnotation(sessionId: string, annotation: Annotation): AnnotationRecord {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n const bucket = this.annotations.get(sessionId)!;\n const record: AnnotationRecord = { ...annotation, sessionId };\n bucket.set(annotation.id, record);\n\n session.seq += 1;\n const seq = session.seq;\n\n this._persist();\n this._emit(sessionId, record, seq);\n return record;\n }\n\n getAnnotation(sessionId: string, annotationId: string): AnnotationRecord | undefined {\n return this.annotations.get(sessionId)?.get(annotationId);\n }\n\n listAnnotations(sessionId: string): AnnotationRecord[] {\n const bucket = this.annotations.get(sessionId);\n if (!bucket) return [];\n return [...bucket.values()].sort((a, b) => a.timestamp - b.timestamp);\n }\n\n updateAnnotation(\n sessionId: string,\n annotationId: string,\n patch: Partial<Annotation>,\n ): AnnotationRecord {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n const bucket = this.annotations.get(sessionId);\n const existing = bucket?.get(annotationId);\n if (!existing) throw new Error(`Annotation not found: ${annotationId}`);\n\n const updated: AnnotationRecord = { ...existing, ...patch, sessionId };\n bucket!.set(annotationId, updated);\n\n session.seq += 1;\n const seq = session.seq;\n\n this._persist();\n this._emit(sessionId, updated, seq);\n return updated;\n }\n\n /* ── SSE subscriptions ────────────────────────────────────────────── */\n\n subscribe(\n sessionId: string,\n cb: (annotation: AnnotationRecord, seq: number) => void,\n ): () => void {\n if (!this.listeners.has(sessionId)) {\n this.listeners.set(sessionId, new Set());\n }\n this.listeners.get(sessionId)!.add(cb);\n return () => this.listeners.get(sessionId)?.delete(cb);\n }\n\n private _emit(sessionId: string, annotation: AnnotationRecord, seq: number): void {\n this.listeners.get(sessionId)?.forEach((cb) => cb(annotation, seq));\n }\n\n /* ── Persistence ──────────────────────────────────────────────────── */\n\n private _persist(): void {\n try {\n const dir = path.dirname(this.storePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n\n const data: PersistedData = {\n sessions: [...this.sessions.values()],\n annotations: [...this.annotations.values()].flatMap((m) => [...m.values()]),\n };\n fs.writeFileSync(this.storePath, JSON.stringify(data, null, 2), \"utf-8\");\n } catch {\n // Persistence failures are non-fatal — data is still in-memory.\n }\n }\n\n private _load(): void {\n try {\n if (!fs.existsSync(this.storePath)) return;\n const raw = fs.readFileSync(this.storePath, \"utf-8\");\n const data: PersistedData = JSON.parse(raw);\n\n for (const session of data.sessions ?? []) {\n this.sessions.set(session.id, session);\n this.annotations.set(session.id, new Map());\n }\n for (const annotation of data.annotations ?? []) {\n this.annotations.get(annotation.sessionId)?.set(annotation.id, annotation);\n }\n } catch {\n // Corrupted or missing file — start fresh.\n }\n }\n}\n","/**\n * HTTP bridge — lets the in-browser <Clickly /> component talk to this server.\n *\n * Endpoints\n * ─────────\n * POST /sessions\n * Body: { url: string }\n * Returns: { sessionId: string }\n *\n * POST /sessions/:sessionId/annotations\n * Body: Annotation (AFS 1.1)\n * Returns: { ok: true, id: string }\n *\n * GET /sessions/:sessionId/annotations\n * Returns: Annotation[]\n *\n * GET /sessions/:sessionId/events\n * Server-Sent Events stream — emits `annotation` events on every add/update.\n * Each event: `data: { annotation, seq }\\n\\n`\n *\n * GET /sessions\n * Returns: Session[]\n *\n * GET /health\n * Returns: { ok: true, version: string }\n */\n\nimport http from \"node:http\";\nimport type { AnnotationStore } from \"./store.js\";\n\nconst VERSION = \"1.0.0\";\n\n/* ─── CORS helper ────────────────────────────────────────────────────── */\n\nfunction cors(res: http.ServerResponse): void {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n}\n\n/* ─── Response helpers ───────────────────────────────────────────────── */\n\nfunction json(res: http.ServerResponse, status: number, body: unknown): void {\n cors(res);\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n });\n res.end(payload);\n}\n\nfunction notFound(res: http.ServerResponse): void {\n json(res, 404, { error: \"not_found\" });\n}\n\nfunction badRequest(res: http.ServerResponse, message: string): void {\n json(res, 400, { error: \"bad_request\", message });\n}\n\n/* ─── Body reader ────────────────────────────────────────────────────── */\n\nfunction readBody(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => {\n try {\n resolve(raw ? JSON.parse(raw) : {});\n } catch {\n reject(new Error(\"Invalid JSON body\"));\n }\n });\n req.on(\"error\", reject);\n });\n}\n\n/* ─── Router ─────────────────────────────────────────────────────────── */\n\nfunction route(\n req: http.IncomingMessage,\n): { method: string; segments: string[] } | null {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const segments = url.pathname.replace(/^\\//, \"\").split(\"/\").filter(Boolean);\n return { method: req.method?.toUpperCase() ?? \"GET\", segments };\n}\n\n/* ─── Bridge factory ─────────────────────────────────────────────────── */\n\nexport interface BridgeHandle {\n port: number;\n close(): Promise<void>;\n}\n\nexport function createHttpBridge(store: AnnotationStore, port: number): BridgeHandle {\n const server = http.createServer(async (req, res) => {\n // Preflight\n if (req.method === \"OPTIONS\") {\n cors(res);\n res.writeHead(204);\n res.end();\n return;\n }\n\n const r = route(req);\n if (!r) return notFound(res);\n const { method, segments } = r;\n\n try {\n // GET /health\n if (method === \"GET\" && segments[0] === \"health\") {\n return json(res, 200, { ok: true, version: VERSION });\n }\n\n // GET /sessions\n if (method === \"GET\" && segments.length === 1 && segments[0] === \"sessions\") {\n return json(res, 200, store.listSessions());\n }\n\n // POST /sessions\n if (method === \"POST\" && segments.length === 1 && segments[0] === \"sessions\") {\n const body = (await readBody(req)) as { url?: string };\n if (!body.url) return badRequest(res, \"url is required\");\n const session = store.createSession(body.url);\n return json(res, 201, { sessionId: session.id });\n }\n\n // Routes under /sessions/:sessionId/...\n if (segments[0] === \"sessions\" && segments[1]) {\n const sessionId = segments[1];\n const session = store.getSession(sessionId);\n if (!session) return json(res, 404, { error: \"session_not_found\" });\n\n const sub = segments[2];\n\n // GET /sessions/:id/annotations\n if (method === \"GET\" && sub === \"annotations\" && segments.length === 3) {\n return json(res, 200, store.listAnnotations(sessionId));\n }\n\n // POST /sessions/:id/annotations\n if (method === \"POST\" && sub === \"annotations\" && segments.length === 3) {\n const body = await readBody(req);\n if (!body || typeof body !== \"object\") return badRequest(res, \"body required\");\n const annotation = body as Record<string, unknown>;\n if (!annotation.id || !annotation.comment || !annotation.elementPath) {\n return badRequest(res, \"id, comment, elementPath are required\");\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const record = store.addAnnotation(sessionId, annotation as any);\n return json(res, 201, { ok: true, id: record.id });\n }\n\n // GET /sessions/:id/events — SSE\n if (method === \"GET\" && sub === \"events\" && segments.length === 3) {\n cors(res);\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\", // disable nginx buffering\n });\n\n // Send current sequence as a \"connected\" event\n res.write(`event: connected\\ndata: ${JSON.stringify({ seq: session.seq })}\\n\\n`);\n\n // Subscribe to future annotations\n const unsubscribe = store.subscribe(sessionId, (annotation, seq) => {\n const payload = JSON.stringify({ annotation, seq });\n res.write(`event: annotation\\ndata: ${payload}\\n\\n`);\n });\n\n // Heartbeat every 15s to keep connection alive through proxies\n const heartbeat = setInterval(() => {\n res.write(`: heartbeat\\n\\n`);\n }, 15_000);\n\n req.on(\"close\", () => {\n clearInterval(heartbeat);\n unsubscribe();\n });\n return; // SSE — don't close\n }\n }\n\n notFound(res);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"internal_error\";\n json(res, 500, { error: \"internal_error\", message });\n }\n });\n\n server.listen(port);\n\n return {\n port,\n close: () =>\n new Promise((resolve, reject) =>\n server.close((err) => (err ? reject(err) : resolve())),\n ),\n };\n}\n","/**\n * @useclickly/mcp-server — real implementation.\n *\n * Exposes a Model Context Protocol stdio server backed by an in-process\n * HTTP bridge. AI coding agents connect via stdio; the browser component\n * connects via HTTP on localhost:4747.\n *\n * MCP tools\n * ─────────\n * clickly_list_sessions List all annotation sessions\n * clickly_list_annotations List annotations for a session\n * clickly_list_layout_changes List Layout Mode annotations only (placement/rearrange)\n * clickly_get_annotation Fetch a single annotation by ID\n * clickly_acknowledge Mark annotation as acknowledged\n * clickly_resolve Mark annotation as resolved\n * clickly_dismiss Mark annotation as dismissed\n * clickly_reply Append a thread message to an annotation\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { AnnotationStore } from \"./store.js\";\nimport { createHttpBridge } from \"./http-bridge.js\";\n\nexport { AnnotationStore } from \"./store.js\";\n\n/* ─── Public types ───────────────────────────────────────────────────── */\n\nexport interface ServerOptions {\n /** HTTP port for the browser bridge. Default: 4747. */\n port?: number;\n /** On-disk path for session persistence. Default: ~/.clickly/sessions.json. */\n storePath?: string;\n /** Custom store instance (useful for testing). */\n store?: AnnotationStore;\n}\n\nexport interface ServerHandle {\n port: number;\n close(): Promise<void>;\n}\n\n/* ─── Shared input schemas ───────────────────────────────────────────── */\n\nconst SessionIdSchema = {\n sessionId: z.string().describe(\"Session ID returned by the browser bridge\"),\n};\n\nconst AnnotationIdSchema = {\n ...SessionIdSchema,\n annotationId: z.string().describe(\"Annotation ID\"),\n};\n\n/* ─── Helper ─────────────────────────────────────────────────────────── */\n\nfunction errResult(message: string) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: message }],\n };\n}\n\nfunction okResult(data: unknown) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof data === \"string\" ? data : JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n\n/* ─── createServer ───────────────────────────────────────────────────── */\n\nexport async function createServer(options: ServerOptions = {}): Promise<ServerHandle> {\n const port = options.port ?? 4747;\n const store =\n options.store ?? new AnnotationStore({ storePath: options.storePath });\n\n /* ── HTTP bridge (browser → server) ─── */\n const bridge = createHttpBridge(store, port);\n\n /* ── MCP server (stdio, agent → server) ─── */\n const mcp = new McpServer(\n { name: \"clickly\", version: \"1.0.0\" },\n {\n capabilities: { tools: {} },\n instructions:\n \"Clickly exposes UI annotations captured in a running React development app. \" +\n \"Use these tools to read feedback, acknowledge issues, resolve them, or reply \" +\n \"to start a conversation thread with the developer.\",\n },\n );\n\n /* ── clickly_list_sessions ─── */\n mcp.registerTool(\n \"clickly_list_sessions\",\n {\n description:\n \"List all Clickly annotation sessions. Each session corresponds to a page/URL \" +\n \"where the developer opened the Clickly toolbar. Returns session IDs, URLs, \" +\n \"creation timestamps, and annotation counts.\",\n inputSchema: {},\n },\n async () => {\n const sessions = store.listSessions();\n const rows = sessions.map((s) => ({\n sessionId: s.id,\n url: s.url,\n createdAt: new Date(s.createdAt).toISOString(),\n annotationCount: store.listAnnotations(s.id).length,\n }));\n return okResult(rows);\n },\n );\n\n /* ── clickly_list_annotations ─── */\n mcp.registerTool(\n \"clickly_list_annotations\",\n {\n description:\n \"List all annotations in a session. Includes element path, position, React \" +\n \"component tree, source file/line, feedback comment, and lifecycle status.\",\n inputSchema: SessionIdSchema,\n },\n async ({ sessionId }) => {\n const session = store.getSession(sessionId);\n if (!session) return errResult(`Session not found: ${sessionId}`);\n return okResult(store.listAnnotations(sessionId));\n },\n );\n\n /* ── clickly_list_layout_changes ─── */\n mcp.registerTool(\n \"clickly_list_layout_changes\",\n {\n description:\n \"List Layout Mode annotations only — placements (new components the developer \" +\n \"wants added) and rearranges (existing on-page sections they want moved). \" +\n \"Use this when you want to focus on structural / design changes without \" +\n \"scrolling past feedback annotations. Returns the same AFS 1.1 records as \" +\n \"clickly_list_annotations, filtered by `kind`.\",\n inputSchema: {\n ...SessionIdSchema,\n kind: z\n .enum([\"placement\", \"rearrange\", \"both\"])\n .optional()\n .describe(\"Filter to just placements, just rearranges, or both (default)\"),\n status: z\n .enum([\"pending\", \"acknowledged\", \"resolved\", \"dismissed\"])\n .optional()\n .describe(\"Optional status filter — useful for ignoring resolved layouts\"),\n },\n },\n async ({ sessionId, kind = \"both\", status }) => {\n const session = store.getSession(sessionId);\n if (!session) return errResult(`Session not found: ${sessionId}`);\n\n const all = store.listAnnotations(sessionId);\n const filtered = all.filter((a) => {\n // Match the requested kind. \"both\" passes either layout kind through.\n const isLayout = a.kind === \"placement\" || a.kind === \"rearrange\";\n if (!isLayout) return false;\n if (kind !== \"both\" && a.kind !== kind) return false;\n if (status && a.status !== status) return false;\n return true;\n });\n\n return okResult(filtered);\n },\n );\n\n /* ── clickly_get_annotation ─── */\n mcp.registerTool(\n \"clickly_get_annotation\",\n {\n description:\n \"Fetch a single annotation by ID. Returns the full AFS 1.1 record including \" +\n \"element metadata, bounding box, React component chain, source location, \" +\n \"and any thread messages.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n const annotation = store.getAnnotation(sessionId, annotationId);\n if (!annotation) {\n return errResult(\n `Annotation not found: ${annotationId} in session ${sessionId}`,\n );\n }\n return okResult(annotation);\n },\n );\n\n /* ── clickly_acknowledge ─── */\n mcp.registerTool(\n \"clickly_acknowledge\",\n {\n description:\n \"Mark an annotation as 'acknowledged' — you have seen it and are working on it. \" +\n \"The developer's UI will show an acknowledged badge on the annotation pin.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n try {\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"acknowledged\",\n });\n return okResult(\n `Acknowledged annotation ${updated.id} in session ${sessionId}.`,\n );\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* ── clickly_resolve ─── */\n mcp.registerTool(\n \"clickly_resolve\",\n {\n description:\n \"Mark an annotation as 'resolved' — the issue has been fixed. Optionally include \" +\n \"a resolution note. The pin will show as resolved in the developer's browser.\",\n inputSchema: {\n ...AnnotationIdSchema,\n note: z\n .string()\n .optional()\n .describe(\"Optional resolution note to append to the thread\"),\n },\n },\n async ({ sessionId, annotationId, note }) => {\n try {\n const existing = store.getAnnotation(sessionId, annotationId);\n if (!existing) return errResult(`Annotation not found: ${annotationId}`);\n\n const thread = existing.thread ? [...existing.thread] : [];\n if (note) {\n thread.push({\n id: crypto.randomUUID(),\n role: \"agent\",\n content: note,\n timestamp: Date.now(),\n });\n }\n\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"resolved\",\n resolvedAt: new Date().toISOString(),\n resolvedBy: \"agent\",\n ...(note ? { thread } : {}),\n });\n return okResult(\n `Resolved annotation ${updated.id}.${note ? ` Note: \"${note}\"` : \"\"}`,\n );\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* ── clickly_dismiss ─── */\n mcp.registerTool(\n \"clickly_dismiss\",\n {\n description:\n \"Mark an annotation as 'dismissed' — it is not actionable or is a duplicate. \" +\n \"Dismissed annotations are hidden from the default list view.\",\n inputSchema: AnnotationIdSchema,\n },\n async ({ sessionId, annotationId }) => {\n try {\n const updated = store.updateAnnotation(sessionId, annotationId, {\n status: \"dismissed\",\n });\n return okResult(`Dismissed annotation ${updated.id}.`);\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* ── clickly_reply ─── */\n mcp.registerTool(\n \"clickly_reply\",\n {\n description:\n \"Append a thread message to an annotation. Use this to ask a clarifying question, \" +\n \"describe what you changed, or communicate back to the developer who left feedback.\",\n inputSchema: {\n ...AnnotationIdSchema,\n message: z.string().describe(\"Your reply message\"),\n },\n },\n async ({ sessionId, annotationId, message }) => {\n try {\n const existing = store.getAnnotation(sessionId, annotationId);\n if (!existing) return errResult(`Annotation not found: ${annotationId}`);\n\n const thread = [\n ...(existing.thread ?? []),\n {\n id: crypto.randomUUID(),\n role: \"agent\" as const,\n content: message,\n timestamp: Date.now(),\n },\n ];\n\n store.updateAnnotation(sessionId, annotationId, { thread });\n return okResult(`Reply added to annotation ${annotationId}.`);\n } catch (err) {\n return errResult(err instanceof Error ? err.message : String(err));\n }\n },\n );\n\n /* ── Connect stdio transport ─── */\n const transport = new StdioServerTransport();\n await mcp.connect(transport);\n\n return {\n port,\n close: async () => {\n await mcp.close();\n await bridge.close();\n },\n };\n}\n","/**\n * `clickly-mcp` CLI\n *\n * Subcommands:\n * server Start the MCP stdio server + HTTP bridge on :4747\n * doctor Check environment: Node version, port availability, store writability\n * init Print (or write) the claude_desktop_config.json / .cursor/mcp.json snippet\n */\n\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { createServer } from \"./server.js\";\n\nconst [, , cmd, ...args] = process.argv;\n\n/* ─── Helpers ────────────────────────────────────────────────────────── */\n\nfunction log(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nfunction out(msg: string): void {\n process.stdout.write(msg + \"\\n\");\n}\n\nfunction parseFlag(flag: string, fallback: string): string {\n const idx = args.indexOf(flag);\n return idx !== -1 && args[idx + 1] != null ? (args[idx + 1] as string) : fallback;\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = net.createServer();\n srv.once(\"error\", () => resolve(false));\n srv.once(\"listening\", () => srv.close(() => resolve(true)));\n srv.listen(port, \"127.0.0.1\");\n });\n}\n\n/* ─── server ─────────────────────────────────────────────────────────── */\n\nasync function cmdServer(): Promise<void> {\n const port = parseInt(parseFlag(\"--port\", \"4747\"), 10);\n const storePath = parseFlag(\"--store\", path.join(os.homedir(), \".clickly\", \"sessions.json\"));\n\n log(`[clickly-mcp] Starting MCP server (stdio) + HTTP bridge on :${port}`);\n log(`[clickly-mcp] Persistence: ${storePath}`);\n log(`[clickly-mcp] Ready. Connect your AI agent via stdio.`);\n\n const handle = await createServer({ port, storePath });\n\n process.on(\"SIGINT\", async () => {\n log(\"\\n[clickly-mcp] Shutting down…\");\n await handle.close();\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", async () => {\n await handle.close();\n process.exit(0);\n });\n}\n\n/* ─── doctor ─────────────────────────────────────────────────────────── */\n\nasync function cmdDoctor(): Promise<void> {\n let allOk = true;\n const port = parseInt(parseFlag(\"--port\", \"4747\"), 10);\n const storePath = path.join(os.homedir(), \".clickly\", \"sessions.json\");\n\n out(\"clickly-mcp doctor\\n\");\n\n // Node version\n const nodeVersion = process.versions.node;\n const major = parseInt(nodeVersion.split(\".\")[0] ?? \"0\", 10);\n const nodeOk = major >= 18;\n out(` Node.js: ${nodeVersion} ${nodeOk ? \"✓\" : \"✗ (need ≥ 18)\"}`);\n if (!nodeOk) allOk = false;\n\n // Port availability\n const portFree = await isPortFree(port);\n out(` Port ${port}: ${portFree ? \"available ✓\" : \"IN USE ✗ (another process is running on this port)\"}`);\n if (!portFree) allOk = false;\n\n // Store directory writability\n const storeDir = path.dirname(storePath);\n let storeOk = false;\n try {\n fs.mkdirSync(storeDir, { recursive: true });\n const testFile = path.join(storeDir, \".write-test\");\n fs.writeFileSync(testFile, \"ok\");\n fs.unlinkSync(testFile);\n storeOk = true;\n } catch {\n storeOk = false;\n }\n out(` Store dir (${storeDir}): ${storeOk ? \"writable ✓\" : \"NOT writable ✗\"}`);\n if (!storeOk) allOk = false;\n\n // Existing sessions\n if (fs.existsSync(storePath)) {\n try {\n const data = JSON.parse(fs.readFileSync(storePath, \"utf-8\"));\n const count = (data.sessions ?? []).length;\n out(` Persisted sessions: ${count}`);\n } catch {\n out(` Persisted sessions: (could not parse ${storePath})`);\n }\n } else {\n out(` Persisted sessions: none yet (store will be created on first use)`);\n }\n\n out(`\\n ${allOk ? \"All checks passed ✓\" : \"Some checks failed ✗ — fix the issues above before starting\"}`);\n process.exit(allOk ? 0 : 1);\n}\n\n/* ─── init ───────────────────────────────────────────────────────────── */\n\nfunction cmdInit(): void {\n const write = args.includes(\"--write\");\n const binPath = process.execPath; // node\n const mcpBin = new URL(\"../dist/cli.js\", import.meta.url).pathname;\n\n // Claude Code / claude_desktop_config.json snippet\n const claudeSnippet = JSON.stringify(\n {\n mcpServers: {\n clickly: {\n command: \"node\",\n args: [mcpBin, \"server\"],\n env: {},\n },\n },\n },\n null,\n 2,\n );\n\n // Cursor .cursor/mcp.json snippet\n const cursorSnippet = JSON.stringify(\n {\n mcpServers: {\n clickly: {\n command: \"node\",\n args: [mcpBin, \"server\"],\n },\n },\n },\n null,\n 2,\n );\n\n if (!write) {\n out(\"# Clickly MCP — setup snippets\\n\");\n out(\"## Claude Code (~/.claude.json or claude_desktop_config.json)\\n\");\n out(claudeSnippet);\n out(\"\\n## Cursor (.cursor/mcp.json)\\n\");\n out(cursorSnippet);\n out(\n \"\\nRun `clickly-mcp init --write` to automatically write the Claude Code config.\\n\",\n );\n out(`(node binary: ${binPath})`);\n return;\n }\n\n // --write: merge into Claude Code config\n const claudeConfigDir = path.join(os.homedir(), \".claude\");\n const claudeConfigPath = path.join(claudeConfigDir, \"claude_desktop_config.json\");\n\n try {\n fs.mkdirSync(claudeConfigDir, { recursive: true });\n\n let existing: Record<string, unknown> = {};\n if (fs.existsSync(claudeConfigPath)) {\n existing = JSON.parse(fs.readFileSync(claudeConfigPath, \"utf-8\"));\n }\n\n const mcpServers = (existing.mcpServers as Record<string, unknown>) ?? {};\n mcpServers.clickly = { command: \"node\", args: [mcpBin, \"server\"], env: {} };\n existing.mcpServers = mcpServers;\n\n fs.writeFileSync(claudeConfigPath, JSON.stringify(existing, null, 2), \"utf-8\");\n out(`✓ Written to ${claudeConfigPath}`);\n out(\" Restart Claude Code (or your agent) to pick up the new MCP server.\");\n } catch (err) {\n out(`✗ Could not write config: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\n/* ─── Dispatch ───────────────────────────────────────────────────────── */\n\nswitch (cmd) {\n case \"server\":\n cmdServer().catch((err) => {\n log(`[clickly-mcp] Fatal: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n });\n break;\n\n case \"doctor\":\n cmdDoctor().catch((err) => {\n log(`doctor error: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n });\n break;\n\n case \"init\":\n cmdInit();\n break;\n\n default:\n out(\"clickly-mcp — MCP server for the Clickly annotation toolbar\\n\");\n out(\"Usage: clickly-mcp <command> [options]\\n\");\n out(\"Commands:\");\n out(\" server Start the MCP stdio server (+ HTTP bridge on :4747)\");\n out(\" Options: --port <n> HTTP bridge port (default: 4747)\");\n out(\" --store <path> Persistence file (default: ~/.clickly/sessions.json)\");\n out(\" doctor Check environment (Node version, port, store writability)\");\n out(\" init Print config snippets for Claude Code / Cursor\");\n out(\" Options: --write Write directly to ~/.claude/claude_desktop_config.json\");\n process.exit(cmd ? 1 : 0);\n}\n"]}