@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 +21 -0
- package/dist/cli.cjs +73 -70
- package/dist/cli.cjs.map +1 -7
- package/dist/cli.js +43 -25
- package/dist/cli.js.map +1 -7
- package/dist/index.cjs +58 -65
- package/dist/index.cjs.map +1 -7
- package/dist/index.d.cts +99 -6
- package/dist/index.d.ts +110 -14
- package/dist/index.js +37 -16
- package/dist/index.js.map +1 -7
- package/package.json +12 -12
- package/dist/cli.d.ts +0 -10
- package/dist/cli.d.ts.map +0 -1
- package/dist/http-bridge.d.ts +0 -33
- package/dist/http-bridge.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server.d.ts +0 -33
- package/dist/server.d.ts.map +0 -1
- package/dist/store.d.ts +0 -46
- package/dist/store.d.ts.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts", "../src/server.ts", "../src/store.ts", "../src/http-bridge.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * @useclickly/mcp-server \u2014 public surface (programmatic).\n *\n * Most users run this via the `clickly-mcp` CLI. The exports here let\n * you embed the server in another Node process.\n *\n * Example:\n * ```ts\n * import { createServer } from \"@useclickly/mcp-server\";\n * const handle = await createServer({ port: 4747 });\n * // ... handle.close() when done\n * ```\n */\n\nexport type { ServerOptions, ServerHandle } from \"./server.js\";\nexport { createServer, AnnotationStore } from \"./server.js\";\nexport type { Session, AnnotationRecord, StoreOptions } from \"./store.js\";\nexport type { BridgeHandle } from \"./http-bridge.js\";\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": ["path", "os", "fs", "http"]
|
|
7
|
-
}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/http-bridge.ts","../src/server.ts"],"names":["path","os","fs","http","z","McpServer","StdioServerTransport"],"mappings":"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","file":"index.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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,21 +1,114 @@
|
|
|
1
1
|
import { Annotation } from '@useclickly/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Annotation + Session store.
|
|
5
|
+
*
|
|
6
|
+
* Holds everything in-memory for fast reads.
|
|
7
|
+
* Persists to a JSON file on every mutating operation so the MCP client
|
|
8
|
+
* can survive a server restart without losing annotations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface Session {
|
|
12
|
+
id: string;
|
|
13
|
+
url: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
/** Monotonically increasing counter — incremented on every annotation mutation. */
|
|
16
|
+
seq: number;
|
|
17
|
+
}
|
|
18
|
+
interface AnnotationRecord extends Annotation {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
}
|
|
21
|
+
interface StoreOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Where to persist sessions + annotations.
|
|
24
|
+
* Defaults to `~/.clickly/sessions.json`.
|
|
25
|
+
*/
|
|
26
|
+
storePath?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class AnnotationStore {
|
|
29
|
+
private sessions;
|
|
30
|
+
/** sessionId → (annotationId → AnnotationRecord) */
|
|
31
|
+
private annotations;
|
|
32
|
+
/** Listeners waiting for new annotations in a session (SSE bridge) */
|
|
33
|
+
private listeners;
|
|
34
|
+
readonly storePath: string;
|
|
35
|
+
constructor(options?: StoreOptions);
|
|
36
|
+
createSession(url: string): Session;
|
|
37
|
+
getSession(sessionId: string): Session | undefined;
|
|
38
|
+
listSessions(): Session[];
|
|
39
|
+
addAnnotation(sessionId: string, annotation: Annotation): AnnotationRecord;
|
|
40
|
+
getAnnotation(sessionId: string, annotationId: string): AnnotationRecord | undefined;
|
|
41
|
+
listAnnotations(sessionId: string): AnnotationRecord[];
|
|
42
|
+
updateAnnotation(sessionId: string, annotationId: string, patch: Partial<Annotation>): AnnotationRecord;
|
|
43
|
+
subscribe(sessionId: string, cb: (annotation: AnnotationRecord, seq: number) => void): () => void;
|
|
44
|
+
private _emit;
|
|
45
|
+
private _persist;
|
|
46
|
+
private _load;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @useclickly/mcp-server — real implementation.
|
|
51
|
+
*
|
|
52
|
+
* Exposes a Model Context Protocol stdio server backed by an in-process
|
|
53
|
+
* HTTP bridge. AI coding agents connect via stdio; the browser component
|
|
54
|
+
* connects via HTTP on localhost:4747.
|
|
55
|
+
*
|
|
56
|
+
* MCP tools
|
|
57
|
+
* ─────────
|
|
58
|
+
* clickly_list_sessions List all annotation sessions
|
|
59
|
+
* clickly_list_annotations List annotations for a session
|
|
60
|
+
* clickly_list_layout_changes List Layout Mode annotations only (placement/rearrange)
|
|
61
|
+
* clickly_get_annotation Fetch a single annotation by ID
|
|
62
|
+
* clickly_acknowledge Mark annotation as acknowledged
|
|
63
|
+
* clickly_resolve Mark annotation as resolved
|
|
64
|
+
* clickly_dismiss Mark annotation as dismissed
|
|
65
|
+
* clickly_reply Append a thread message to an annotation
|
|
66
|
+
*/
|
|
67
|
+
|
|
3
68
|
interface ServerOptions {
|
|
4
69
|
/** HTTP port for the browser bridge. Default: 4747. */
|
|
5
70
|
port?: number;
|
|
6
71
|
/** On-disk path for session persistence. Default: ~/.clickly/sessions.json. */
|
|
7
72
|
storePath?: string;
|
|
73
|
+
/** Custom store instance (useful for testing). */
|
|
74
|
+
store?: AnnotationStore;
|
|
8
75
|
}
|
|
9
76
|
interface ServerHandle {
|
|
10
77
|
port: number;
|
|
11
78
|
close(): Promise<void>;
|
|
12
|
-
getAnnotations(sessionId: string): Annotation[];
|
|
13
79
|
}
|
|
80
|
+
declare function createServer(options?: ServerOptions): Promise<ServerHandle>;
|
|
81
|
+
|
|
14
82
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
83
|
+
* HTTP bridge — lets the in-browser <Clickly /> component talk to this server.
|
|
84
|
+
*
|
|
85
|
+
* Endpoints
|
|
86
|
+
* ─────────
|
|
87
|
+
* POST /sessions
|
|
88
|
+
* Body: { url: string }
|
|
89
|
+
* Returns: { sessionId: string }
|
|
90
|
+
*
|
|
91
|
+
* POST /sessions/:sessionId/annotations
|
|
92
|
+
* Body: Annotation (AFS 1.1)
|
|
93
|
+
* Returns: { ok: true, id: string }
|
|
94
|
+
*
|
|
95
|
+
* GET /sessions/:sessionId/annotations
|
|
96
|
+
* Returns: Annotation[]
|
|
97
|
+
*
|
|
98
|
+
* GET /sessions/:sessionId/events
|
|
99
|
+
* Server-Sent Events stream — emits `annotation` events on every add/update.
|
|
100
|
+
* Each event: `data: { annotation, seq }\n\n`
|
|
101
|
+
*
|
|
102
|
+
* GET /sessions
|
|
103
|
+
* Returns: Session[]
|
|
104
|
+
*
|
|
105
|
+
* GET /health
|
|
106
|
+
* Returns: { ok: true, version: string }
|
|
18
107
|
*/
|
|
19
|
-
declare function createServer(_options?: ServerOptions): Promise<ServerHandle>;
|
|
20
108
|
|
|
21
|
-
|
|
109
|
+
interface BridgeHandle {
|
|
110
|
+
port: number;
|
|
111
|
+
close(): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { type AnnotationRecord, AnnotationStore, type BridgeHandle, type ServerHandle, type ServerOptions, type Session, type StoreOptions, createServer };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,114 @@
|
|
|
1
|
+
import { Annotation } from '@useclickly/core';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Annotation + Session store.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Holds everything in-memory for fast reads.
|
|
7
|
+
* Persists to a JSON file on every mutating operation so the MCP client
|
|
8
|
+
* can survive a server restart without losing annotations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface Session {
|
|
12
|
+
id: string;
|
|
13
|
+
url: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
/** Monotonically increasing counter — incremented on every annotation mutation. */
|
|
16
|
+
seq: number;
|
|
17
|
+
}
|
|
18
|
+
interface AnnotationRecord extends Annotation {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
}
|
|
21
|
+
interface StoreOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Where to persist sessions + annotations.
|
|
24
|
+
* Defaults to `~/.clickly/sessions.json`.
|
|
25
|
+
*/
|
|
26
|
+
storePath?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class AnnotationStore {
|
|
29
|
+
private sessions;
|
|
30
|
+
/** sessionId → (annotationId → AnnotationRecord) */
|
|
31
|
+
private annotations;
|
|
32
|
+
/** Listeners waiting for new annotations in a session (SSE bridge) */
|
|
33
|
+
private listeners;
|
|
34
|
+
readonly storePath: string;
|
|
35
|
+
constructor(options?: StoreOptions);
|
|
36
|
+
createSession(url: string): Session;
|
|
37
|
+
getSession(sessionId: string): Session | undefined;
|
|
38
|
+
listSessions(): Session[];
|
|
39
|
+
addAnnotation(sessionId: string, annotation: Annotation): AnnotationRecord;
|
|
40
|
+
getAnnotation(sessionId: string, annotationId: string): AnnotationRecord | undefined;
|
|
41
|
+
listAnnotations(sessionId: string): AnnotationRecord[];
|
|
42
|
+
updateAnnotation(sessionId: string, annotationId: string, patch: Partial<Annotation>): AnnotationRecord;
|
|
43
|
+
subscribe(sessionId: string, cb: (annotation: AnnotationRecord, seq: number) => void): () => void;
|
|
44
|
+
private _emit;
|
|
45
|
+
private _persist;
|
|
46
|
+
private _load;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @useclickly/mcp-server — real implementation.
|
|
51
|
+
*
|
|
52
|
+
* Exposes a Model Context Protocol stdio server backed by an in-process
|
|
53
|
+
* HTTP bridge. AI coding agents connect via stdio; the browser component
|
|
54
|
+
* connects via HTTP on localhost:4747.
|
|
55
|
+
*
|
|
56
|
+
* MCP tools
|
|
57
|
+
* ─────────
|
|
58
|
+
* clickly_list_sessions List all annotation sessions
|
|
59
|
+
* clickly_list_annotations List annotations for a session
|
|
60
|
+
* clickly_list_layout_changes List Layout Mode annotations only (placement/rearrange)
|
|
61
|
+
* clickly_get_annotation Fetch a single annotation by ID
|
|
62
|
+
* clickly_acknowledge Mark annotation as acknowledged
|
|
63
|
+
* clickly_resolve Mark annotation as resolved
|
|
64
|
+
* clickly_dismiss Mark annotation as dismissed
|
|
65
|
+
* clickly_reply Append a thread message to an annotation
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
interface ServerOptions {
|
|
69
|
+
/** HTTP port for the browser bridge. Default: 4747. */
|
|
70
|
+
port?: number;
|
|
71
|
+
/** On-disk path for session persistence. Default: ~/.clickly/sessions.json. */
|
|
72
|
+
storePath?: string;
|
|
73
|
+
/** Custom store instance (useful for testing). */
|
|
74
|
+
store?: AnnotationStore;
|
|
75
|
+
}
|
|
76
|
+
interface ServerHandle {
|
|
77
|
+
port: number;
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
declare function createServer(options?: ServerOptions): Promise<ServerHandle>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* HTTP bridge — lets the in-browser <Clickly /> component talk to this server.
|
|
84
|
+
*
|
|
85
|
+
* Endpoints
|
|
86
|
+
* ─────────
|
|
87
|
+
* POST /sessions
|
|
88
|
+
* Body: { url: string }
|
|
89
|
+
* Returns: { sessionId: string }
|
|
90
|
+
*
|
|
91
|
+
* POST /sessions/:sessionId/annotations
|
|
92
|
+
* Body: Annotation (AFS 1.1)
|
|
93
|
+
* Returns: { ok: true, id: string }
|
|
94
|
+
*
|
|
95
|
+
* GET /sessions/:sessionId/annotations
|
|
96
|
+
* Returns: Annotation[]
|
|
97
|
+
*
|
|
98
|
+
* GET /sessions/:sessionId/events
|
|
99
|
+
* Server-Sent Events stream — emits `annotation` events on every add/update.
|
|
100
|
+
* Each event: `data: { annotation, seq }\n\n`
|
|
101
|
+
*
|
|
102
|
+
* GET /sessions
|
|
103
|
+
* Returns: Session[]
|
|
6
104
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* import { createServer } from "@useclickly/mcp-server";
|
|
10
|
-
* const handle = await createServer({ port: 4747 });
|
|
11
|
-
* // ... handle.close() when done
|
|
12
|
-
* ```
|
|
105
|
+
* GET /health
|
|
106
|
+
* Returns: { ok: true, version: string }
|
|
13
107
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
108
|
+
|
|
109
|
+
interface BridgeHandle {
|
|
110
|
+
port: number;
|
|
111
|
+
close(): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { type AnnotationRecord, AnnotationStore, type BridgeHandle, type ServerHandle, type ServerOptions, type Session, type StoreOptions, createServer };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import { McpServer } from
|
|
3
|
-
import { StdioServerTransport } from
|
|
4
|
-
import { z } from
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import http from 'http';
|
|
5
9
|
|
|
6
|
-
// packages/mcp-server/src/store.ts
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import os from "node:os";
|
|
9
|
-
import path from "node:path";
|
|
10
10
|
var AnnotationStore = class {
|
|
11
11
|
sessions = /* @__PURE__ */ new Map();
|
|
12
12
|
/** sessionId → (annotationId → AnnotationRecord) */
|
|
@@ -108,9 +108,6 @@ var AnnotationStore = class {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
|
-
|
|
112
|
-
// packages/mcp-server/src/http-bridge.ts
|
|
113
|
-
import http from "node:http";
|
|
114
111
|
var VERSION = "1.0.0";
|
|
115
112
|
function cors(res) {
|
|
116
113
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -240,7 +237,7 @@ data: ${payload}
|
|
|
240
237
|
};
|
|
241
238
|
}
|
|
242
239
|
|
|
243
|
-
//
|
|
240
|
+
// src/server.ts
|
|
244
241
|
var SessionIdSchema = {
|
|
245
242
|
sessionId: z.string().describe("Session ID returned by the browser bridge")
|
|
246
243
|
};
|
|
@@ -304,6 +301,30 @@ async function createServer(options = {}) {
|
|
|
304
301
|
return okResult(store.listAnnotations(sessionId));
|
|
305
302
|
}
|
|
306
303
|
);
|
|
304
|
+
mcp.registerTool(
|
|
305
|
+
"clickly_list_layout_changes",
|
|
306
|
+
{
|
|
307
|
+
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`.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
...SessionIdSchema,
|
|
310
|
+
kind: z.enum(["placement", "rearrange", "both"]).optional().describe("Filter to just placements, just rearranges, or both (default)"),
|
|
311
|
+
status: z.enum(["pending", "acknowledged", "resolved", "dismissed"]).optional().describe("Optional status filter \u2014 useful for ignoring resolved layouts")
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
async ({ sessionId, kind = "both", status }) => {
|
|
315
|
+
const session = store.getSession(sessionId);
|
|
316
|
+
if (!session) return errResult(`Session not found: ${sessionId}`);
|
|
317
|
+
const all = store.listAnnotations(sessionId);
|
|
318
|
+
const filtered = all.filter((a) => {
|
|
319
|
+
const isLayout = a.kind === "placement" || a.kind === "rearrange";
|
|
320
|
+
if (!isLayout) return false;
|
|
321
|
+
if (kind !== "both" && a.kind !== kind) return false;
|
|
322
|
+
if (status && a.status !== status) return false;
|
|
323
|
+
return true;
|
|
324
|
+
});
|
|
325
|
+
return okResult(filtered);
|
|
326
|
+
}
|
|
327
|
+
);
|
|
307
328
|
mcp.registerTool(
|
|
308
329
|
"clickly_get_annotation",
|
|
309
330
|
{
|
|
@@ -431,7 +452,7 @@ async function createServer(options = {}) {
|
|
|
431
452
|
}
|
|
432
453
|
};
|
|
433
454
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
455
|
+
|
|
456
|
+
export { AnnotationStore, createServer };
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
458
|
+
//# sourceMappingURL=index.js.map
|