browser-lens-mcp 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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -0
  3. package/dist/src/index.d.ts +3 -0
  4. package/dist/src/index.d.ts.map +1 -0
  5. package/dist/src/index.js +25 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/prompts/index.d.ts +4 -0
  8. package/dist/src/prompts/index.d.ts.map +1 -0
  9. package/dist/src/prompts/index.js +180 -0
  10. package/dist/src/prompts/index.js.map +1 -0
  11. package/dist/src/resources/index.d.ts +4 -0
  12. package/dist/src/resources/index.d.ts.map +1 -0
  13. package/dist/src/resources/index.js +64 -0
  14. package/dist/src/resources/index.js.map +1 -0
  15. package/dist/src/server.d.ts +4 -0
  16. package/dist/src/server.d.ts.map +1 -0
  17. package/dist/src/server.js +15 -0
  18. package/dist/src/server.js.map +1 -0
  19. package/dist/src/store/browser-store.d.ts +45 -0
  20. package/dist/src/store/browser-store.d.ts.map +1 -0
  21. package/dist/src/store/browser-store.js +240 -0
  22. package/dist/src/store/browser-store.js.map +1 -0
  23. package/dist/src/store/types.d.ts +342 -0
  24. package/dist/src/store/types.d.ts.map +1 -0
  25. package/dist/src/store/types.js +3 -0
  26. package/dist/src/store/types.js.map +1 -0
  27. package/dist/src/tools/index.d.ts +4 -0
  28. package/dist/src/tools/index.d.ts.map +1 -0
  29. package/dist/src/tools/index.js +889 -0
  30. package/dist/src/tools/index.js.map +1 -0
  31. package/dist/src/transport/connector-script.d.ts +2 -0
  32. package/dist/src/transport/connector-script.d.ts.map +1 -0
  33. package/dist/src/transport/connector-script.js +336 -0
  34. package/dist/src/transport/connector-script.js.map +1 -0
  35. package/dist/src/transport/http-receiver.d.ts +4 -0
  36. package/dist/src/transport/http-receiver.d.ts.map +1 -0
  37. package/dist/src/transport/http-receiver.js +218 -0
  38. package/dist/src/transport/http-receiver.js.map +1 -0
  39. package/dist/src/transport/ws-receiver.d.ts +4 -0
  40. package/dist/src/transport/ws-receiver.d.ts.map +1 -0
  41. package/dist/src/transport/ws-receiver.js +39 -0
  42. package/dist/src/transport/ws-receiver.js.map +1 -0
  43. package/package.json +79 -0
@@ -0,0 +1,218 @@
1
+ import * as http from "node:http";
2
+ import { getConnectorScript } from "./connector-script.js";
3
+ const CORS_HEADERS = {
4
+ "Access-Control-Allow-Origin": "*",
5
+ "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
6
+ "Access-Control-Allow-Headers": "Content-Type",
7
+ "Access-Control-Max-Age": "86400",
8
+ };
9
+ function jsonResponse(res, status, data) {
10
+ res.writeHead(status, {
11
+ ...CORS_HEADERS,
12
+ "Content-Type": "application/json",
13
+ });
14
+ res.end(JSON.stringify(data));
15
+ }
16
+ function readBody(req) {
17
+ return new Promise((resolve, reject) => {
18
+ const chunks = [];
19
+ req.on("data", (chunk) => chunks.push(chunk));
20
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
21
+ req.on("error", reject);
22
+ });
23
+ }
24
+ function getConnectorPage(httpPort, wsPort) {
25
+ const script = getConnectorScript(httpPort, wsPort);
26
+ const bookmarklet = `javascript:${encodeURIComponent(script)}`;
27
+ return `<!DOCTYPE html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
31
+ <title>Browser Lens — MCP Visual Inspector</title>
32
+ <style>
33
+ :root{--bg:#0a0a0a;--surface:#18181b;--surface2:#27272a;--border:#3f3f46;--text:#fafafa;--muted:#a1a1aa;--dim:#71717a;--accent:#8b5cf6;--accent2:#7c3aed;--green:#22c55e;--green-bg:#052e16;--green-b:#14532d;--amber:#f59e0b;--red:#ef4444;--cyan:#06b6d4}
34
+ *{margin:0;padding:0;box-sizing:border-box}
35
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:40px 20px}
36
+ .wrap{max-width:720px;margin:0 auto}
37
+ .header{text-align:center;margin-bottom:48px}
38
+ .header h1{font-size:28px;font-weight:800;margin-bottom:8px;letter-spacing:-0.02em;background:linear-gradient(135deg,#8b5cf6,#06b6d4);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
39
+ .header p{color:var(--muted);font-size:15px}
40
+ .server-status{display:flex;gap:16px;margin-bottom:40px}
41
+ .status-card{flex:1;padding:16px;background:var(--surface);border:1px solid var(--green-b);border-radius:10px;display:flex;align-items:center;gap:10px}
42
+ .status-dot{width:10px;height:10px;border-radius:50%;background:var(--green);flex-shrink:0;animation:pulse 2s infinite}
43
+ .status-dot.off{background:var(--red);animation:none}
44
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
45
+ .status-label{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:.05em}
46
+ .status-value{font-size:14px;font-weight:600;font-family:'SF Mono',Monaco,Consolas,monospace}
47
+ .section{margin-bottom:40px}
48
+ .section-title{font-size:18px;font-weight:700;margin-bottom:20px;display:flex;align-items:center;gap:8px}
49
+ .tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;background:#8b5cf622;color:var(--accent);border:1px solid #8b5cf644}
50
+ .steps{display:flex;flex-direction:column;gap:2px}
51
+ .step{display:flex;gap:16px;padding:20px;background:var(--surface);border:1px solid #27272a}
52
+ .step:first-child{border-radius:10px 10px 0 0}
53
+ .step:last-child{border-radius:0 0 10px 10px}
54
+ .step-num{width:28px;height:28px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;flex-shrink:0;color:#fff}
55
+ .step-body h3{font-size:14px;font-weight:600;margin-bottom:6px}
56
+ .step-body p{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:10px}
57
+ .bm-zone{padding:20px;background:var(--bg);border:2px dashed var(--border);border-radius:8px;text-align:center;margin:12px 0}
58
+ .bm-zone p{font-size:12px;color:var(--dim);margin-bottom:12px}
59
+ .bm-btn{display:inline-block;padding:10px 24px;background:linear-gradient(135deg,#8b5cf6,#06b6d4);color:#fff;border-radius:8px;text-decoration:none;font-size:14px;font-weight:700;cursor:grab;transition:all .15s;box-shadow:0 2px 8px #8b5cf644}
60
+ .bm-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #8b5cf666}
61
+ .snippet-box{position:relative;margin:8px 0}
62
+ .snippet-box pre{padding:14px;background:var(--bg);border:1px solid var(--border);border-radius:8px;font-family:'SF Mono',Monaco,Consolas,monospace;font-size:12px;color:var(--muted);overflow-x:auto;white-space:pre-wrap;word-break:break-all;max-height:80px;overflow-y:auto;line-height:1.5}
63
+ .live-status{margin-top:32px;padding:16px 20px;border-radius:10px;font-size:13px;line-height:1.6}
64
+ .live-status.waiting{background:#1c1917;border:1px solid #44403c;color:var(--amber)}
65
+ .live-status.connected{background:var(--green-bg);border:1px solid var(--green-b);color:var(--green)}
66
+ .live-status strong{display:block;margin-bottom:4px;font-size:14px}
67
+ .captures{margin-top:24px;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
68
+ .cap{padding:12px;background:var(--surface);border:1px solid #27272a;border-radius:8px;font-size:12px}
69
+ .cap strong{display:block;font-size:13px;margin-bottom:4px}
70
+ .cap span{color:var(--dim)}
71
+ .footer{text-align:center;margin-top:48px;padding-top:24px;border-top:1px solid #27272a;font-size:12px;color:var(--dim)}
72
+ .footer a{color:var(--accent);text-decoration:none}
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <div class="wrap">
77
+ <div class="header">
78
+ <h1>Browser Lens</h1>
79
+ <p>Real-time DOM, CSS & visual inspection for your IDE's AI agent.</p>
80
+ </div>
81
+ <div class="server-status">
82
+ <div class="status-card"><span class="status-dot"></span><div><div class="status-label">HTTP</div><div class="status-value">:${httpPort}</div></div></div>
83
+ <div class="status-card"><span class="status-dot"></span><div><div class="status-label">WebSocket</div><div class="status-value">:${wsPort}</div></div></div>
84
+ <div class="status-card"><span class="status-dot off" id="app-dot"></span><div><div class="status-label">Browser</div><div class="status-value" id="app-status">Not connected</div></div></div>
85
+ </div>
86
+ <div class="section">
87
+ <div class="section-title">Setup <span class="tag">Zero Code</span></div>
88
+ <div class="steps">
89
+ <div class="step"><div class="step-num">1</div><div class="step-body">
90
+ <h3>Drag bookmarklet to your Bookmarks Bar</h3>
91
+ <div class="bm-zone">
92
+ <p>Click and drag to your bookmarks bar:</p>
93
+ <a class="bm-btn" href="${bookmarklet}" onclick="event.preventDefault();alert('Drag this to your bookmarks bar, then click it on any page.')" title="Drag to bookmarks bar">Browser Lens</a>
94
+ </div>
95
+ </div></div>
96
+ <div class="step"><div class="step-num">2</div><div class="step-body">
97
+ <h3>Open any web page and click the bookmarklet</h3>
98
+ <p>Navigate to any web app. Click <strong>"Browser Lens"</strong> in your bookmarks bar.</p>
99
+ <div class="snippet-box"><pre style="color:var(--green);background:var(--green-bg);border-color:var(--green-b)">[Browser Lens] WebSocket connected
100
+ [Browser Lens] Connected! DOM, CSS, layout, and visual data streaming to IDE.</pre></div>
101
+ </div></div>
102
+ <div class="step"><div class="step-num">3</div><div class="step-body">
103
+ <h3>Ask your IDE's AI agent</h3>
104
+ <div class="snippet-box"><pre><span style="color:var(--accent);font-weight:600">@browser-lens</span> <span style="color:#86efac">Describe this page's UI layout</span>
105
+ <span style="color:var(--accent);font-weight:600">@browser-lens</span> <span style="color:#86efac">Compare this button with Figma specs</span>
106
+ <span style="color:var(--accent);font-weight:600">@browser-lens</span> <span style="color:#86efac">Take a screenshot and audit the design</span>
107
+ <span style="color:var(--accent);font-weight:600">@browser-lens</span> <span style="color:#86efac">What CSS variables are defined?</span></pre></div>
108
+ </div></div>
109
+ </div>
110
+ </div>
111
+ <div class="captures">
112
+ <div class="cap"><strong>DOM Tree</strong><span>Full structure with semantic analysis</span></div>
113
+ <div class="cap"><strong>Computed Styles</strong><span>Every CSS property for any element</span></div>
114
+ <div class="cap"><strong>Layout & Box Model</strong><span>Flex, grid, positioning, spacing</span></div>
115
+ <div class="cap"><strong>Screenshots</strong><span>Viewport & element capture as PNG</span></div>
116
+ <div class="cap"><strong>Typography</strong><span>Fonts, sizes, weights, line-heights</span></div>
117
+ <div class="cap"><strong>Color Palette</strong><span>All colors with usage counts</span></div>
118
+ <div class="cap"><strong>CSS Variables</strong><span>Custom properties from :root</span></div>
119
+ <div class="cap"><strong>Accessibility</strong><span>ARIA, roles, labels, contrast</span></div>
120
+ </div>
121
+ <div id="live-status" class="live-status waiting">
122
+ <strong>Waiting for browser connection...</strong>
123
+ Open any web page and click the bookmarklet.
124
+ </div>
125
+ <div class="footer">
126
+ <p>Browser Lens by <strong>nano-step</strong> &middot; <a href="https://github.com/nano-step/mcp-browser-lens">GitHub</a> &middot; MIT License</p>
127
+ </div>
128
+ </div>
129
+ <script>
130
+ setInterval(function(){
131
+ fetch('/health').then(function(r){return r.json()}).then(function(d){
132
+ var el=document.getElementById('live-status');
133
+ var dot=document.getElementById('app-dot');
134
+ var st=document.getElementById('app-status');
135
+ if(d.elements>0||d.hasDom){
136
+ el.className='live-status connected';
137
+ el.innerHTML='<strong>Connected! '+d.elements+' elements captured.</strong>Your IDE can now inspect the page via MCP protocol.';
138
+ dot.className='status-dot';
139
+ st.textContent=d.elements+' elements';
140
+ }
141
+ }).catch(function(){});
142
+ },3000);
143
+ </script>
144
+ </body>
145
+ </html>`;
146
+ }
147
+ export function createHttpReceiver(store, port, wsPort) {
148
+ const effectiveWsPort = wsPort ?? port + 1;
149
+ const server = http.createServer(async (req, res) => {
150
+ if (req.method === "OPTIONS") {
151
+ res.writeHead(204, CORS_HEADERS);
152
+ res.end();
153
+ return;
154
+ }
155
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
156
+ if (req.method === "GET" && url.pathname === "/") {
157
+ res.writeHead(200, { "Content-Type": "text/html" });
158
+ res.end(getConnectorPage(port, effectiveWsPort));
159
+ return;
160
+ }
161
+ if (req.method === "GET" && url.pathname === "/connector.js") {
162
+ res.writeHead(200, {
163
+ ...CORS_HEADERS,
164
+ "Content-Type": "application/javascript",
165
+ });
166
+ res.end(getConnectorScript(port, effectiveWsPort));
167
+ return;
168
+ }
169
+ if (req.method === "GET" && url.pathname === "/health") {
170
+ const info = store.getPageInfo();
171
+ jsonResponse(res, 200, {
172
+ status: "ok",
173
+ elements: info.totalElements,
174
+ hasDom: info.hasDom,
175
+ screenshots: info.screenshotCount,
176
+ httpPort: port,
177
+ wsPort: effectiveWsPort,
178
+ uptime: process.uptime(),
179
+ connectorUrl: `http://localhost:${port}`,
180
+ });
181
+ return;
182
+ }
183
+ if (req.method === "POST" && url.pathname === "/ingest") {
184
+ try {
185
+ const body = await readBody(req);
186
+ const payload = JSON.parse(body);
187
+ if (!payload.timestamp) {
188
+ jsonResponse(res, 400, { error: "Missing timestamp" });
189
+ return;
190
+ }
191
+ store.ingest(payload);
192
+ jsonResponse(res, 200, {
193
+ ok: true,
194
+ elements: store.getElementCount(),
195
+ });
196
+ }
197
+ catch {
198
+ jsonResponse(res, 400, { error: "Invalid JSON payload" });
199
+ }
200
+ return;
201
+ }
202
+ jsonResponse(res, 404, { error: "Not found" });
203
+ });
204
+ server.on("error", (err) => {
205
+ if (err.code === "EADDRINUSE") {
206
+ process.stderr.write(`[mcp-browser-lens] Port ${port} already in use — HTTP disabled.\n`);
207
+ }
208
+ else {
209
+ process.stderr.write(`[mcp-browser-lens] HTTP error: ${err.message}\n`);
210
+ }
211
+ });
212
+ server.listen(port, "0.0.0.0", () => {
213
+ process.stderr.write(`[mcp-browser-lens] HTTP listening on http://localhost:${port}\n`);
214
+ process.stderr.write(`[mcp-browser-lens] Open http://localhost:${port} to get the bookmarklet\n`);
215
+ });
216
+ return server;
217
+ }
218
+ //# sourceMappingURL=http-receiver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-receiver.js","sourceRoot":"","sources":["../../../src/transport/http-receiver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { WebSocketServer } from "ws";
2
+ import type { BrowserStore } from "../store/browser-store.js";
3
+ export declare function createWsReceiver(store: BrowserStore, port: number): WebSocketServer;
4
+ //# sourceMappingURL=ws-receiver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-receiver.d.ts","sourceRoot":"","sources":["../../../src/transport/ws-receiver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,GACX,eAAe,CAsDjB"}
@@ -0,0 +1,39 @@
1
+ import { WebSocketServer } from "ws";
2
+ export function createWsReceiver(store, port) {
3
+ const wss = new WebSocketServer({ port });
4
+ wss.on("connection", (ws) => {
5
+ process.stderr.write(`[mcp-browser-lens] WebSocket client connected (total: ${wss.clients.size})\n`);
6
+ ws.on("message", (raw) => {
7
+ try {
8
+ const payload = JSON.parse(raw.toString());
9
+ if (payload.timestamp) {
10
+ store.ingest(payload);
11
+ }
12
+ ws.send(JSON.stringify({ ok: true, elements: store.getElementCount() }));
13
+ }
14
+ catch {
15
+ ws.send(JSON.stringify({ error: "Invalid JSON" }));
16
+ }
17
+ });
18
+ ws.on("close", () => {
19
+ process.stderr.write(`[mcp-browser-lens] WebSocket client disconnected (remaining: ${wss.clients.size})\n`);
20
+ if (wss.clients.size === 0) {
21
+ store.clear();
22
+ process.stderr.write(`[mcp-browser-lens] All clients disconnected — store cleared.\n`);
23
+ }
24
+ });
25
+ });
26
+ wss.on("error", (err) => {
27
+ if (err.code === "EADDRINUSE") {
28
+ process.stderr.write(`[mcp-browser-lens] WS port ${port} already in use — disabled.\n`);
29
+ }
30
+ else {
31
+ process.stderr.write(`[mcp-browser-lens] WebSocket error: ${err.message}\n`);
32
+ }
33
+ });
34
+ wss.on("listening", () => {
35
+ process.stderr.write(`[mcp-browser-lens] WebSocket listening on port ${port}\n`);
36
+ });
37
+ return wss;
38
+ }
39
+ //# sourceMappingURL=ws-receiver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-receiver.js","sourceRoot":"","sources":["../../../src/transport/ws-receiver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAIrC,MAAM,UAAU,gBAAgB,CAC9B,KAAmB,EACnB,IAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yDAAyD,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAC/E,CAAC;QAEF,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAkB,CAAC;gBAC5D,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;oBACtB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC;gBACD,EAAE,CAAC,IAAI,CACL,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,eAAe,EAAE,EAAE,CAAC,CAChE,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gEAAgE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CACtF,CAAC;YACF,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gEAAgE,CACjE,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;QAC7C,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8BAA8B,IAAI,+BAA+B,CAClE,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uCAAuC,GAAG,CAAC,OAAO,IAAI,CACvD,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kDAAkD,IAAI,IAAI,CAC3D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "browser-lens-mcp",
3
+ "version": "1.1.0",
4
+ "description": "MCP server that connects to your browser for real-time DOM, CSS, layout inspection, screenshot capture, and Figma design comparison — your IDE's AI agent sees exactly what users see",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "types": "dist/src/index.d.ts",
8
+ "bin": {
9
+ "browser-lens-mcp": "./dist/src/index.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/src/index.js",
14
+ "types": "./dist/src/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "start": "node dist/src/index.js",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "browser",
28
+ "dom",
29
+ "css",
30
+ "inspector",
31
+ "figma",
32
+ "design-comparison",
33
+ "screenshot",
34
+ "ui-audit",
35
+ "frontend",
36
+ "developer-tools",
37
+ "real-time",
38
+ "websocket",
39
+ "layout",
40
+ "accessibility",
41
+ "visual-testing",
42
+ "ide",
43
+ "ai-agent",
44
+ "cursor",
45
+ "vscode",
46
+ "claude-code"
47
+ ],
48
+ "author": {
49
+ "name": "Hoai Nho Nguyen",
50
+ "email": "nhoxtvt@gmail.com",
51
+ "url": "https://github.com/nano-step"
52
+ },
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/nano-step/mcp-browser-lens.git"
57
+ },
58
+ "homepage": "https://nano-step.github.io/mcp-browser-lens",
59
+ "bugs": {
60
+ "url": "https://github.com/nano-step/mcp-browser-lens/issues"
61
+ },
62
+ "files": [
63
+ "dist",
64
+ "README.md",
65
+ "LICENSE"
66
+ ],
67
+ "dependencies": {
68
+ "@modelcontextprotocol/sdk": "^1.12.1",
69
+ "ws": "^8.19.0"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^20.11.0",
73
+ "@types/ws": "^8.18.1",
74
+ "typescript": "^5.5.0"
75
+ },
76
+ "engines": {
77
+ "node": ">=20.0.0"
78
+ }
79
+ }