@vibevibes/mcp 0.7.0 → 0.8.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/dist/index.js CHANGED
@@ -640,6 +640,35 @@ Use this to orient yourself before deciding whether to act.`, {
640
640
  return { content: [{ type: "text", text: `Look failed: ${toErrorMessage(err)}` }] };
641
641
  }
642
642
  });
643
+ // ── Tool: screenshot ───────────────────────────────────────
644
+ server.tool("screenshot", `Capture a screenshot of the experience as seen in the browser.
645
+
646
+ Returns the current visual state as a PNG image. Use this to:
647
+ - See what the UI looks like
648
+ - Debug visual/layout issues
649
+ - Verify your changes rendered correctly
650
+
651
+ Requires at least one browser viewer to be connected.`, {}, async () => {
652
+ try {
653
+ const res = await fetchJSON("/screenshot", { timeoutMs: 15000 });
654
+ if (res.error) {
655
+ return { content: [{ type: "text", text: `Screenshot failed: ${res.error}` }] };
656
+ }
657
+ if (!res.dataUrl) {
658
+ return { content: [{ type: "text", text: "Screenshot returned empty" }] };
659
+ }
660
+ // dataUrl is "data:image/png;base64,..."
661
+ const base64 = res.dataUrl.replace(/^data:image\/\w+;base64,/, "");
662
+ return {
663
+ content: [
664
+ { type: "image", data: base64, mimeType: "image/png" },
665
+ ],
666
+ };
667
+ }
668
+ catch (err) {
669
+ return { content: [{ type: "text", text: `Screenshot failed: ${toErrorMessage(err)}` }] };
670
+ }
671
+ });
643
672
  // ── Tool: disconnect ────────────────────────────────────────
644
673
  server.tool("disconnect", `Disconnect from the current experience.
645
674
 
package/dist/server.js CHANGED
@@ -903,6 +903,64 @@ app.post("/browser-error", (req, res) => {
903
903
  }
904
904
  res.json({ ok: true });
905
905
  });
906
+ // ── Screenshot ─────────────────────────────────────────────
907
+ const screenshotCallbacks = new Map();
908
+ app.get("/screenshot", async (_req, res) => {
909
+ // Find a browser WebSocket connection to request a screenshot from
910
+ let browserWs = null;
911
+ for (const [ws, actorId] of room.wsConnections.entries()) {
912
+ if (ws.readyState === WebSocket.OPEN && actorId.startsWith("viewer-")) {
913
+ browserWs = ws;
914
+ break;
915
+ }
916
+ }
917
+ // Fallback: any open connection
918
+ if (!browserWs) {
919
+ for (const [ws] of room.wsConnections.entries()) {
920
+ if (ws.readyState === WebSocket.OPEN) {
921
+ browserWs = ws;
922
+ break;
923
+ }
924
+ }
925
+ }
926
+ if (!browserWs) {
927
+ return res.status(503).json({ error: "No browser connected to capture screenshot" });
928
+ }
929
+ const id = `ss-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
930
+ try {
931
+ const dataUrl = await new Promise((resolve, reject) => {
932
+ const timer = setTimeout(() => {
933
+ screenshotCallbacks.delete(id);
934
+ reject(new Error("Screenshot timeout"));
935
+ }, 10000);
936
+ screenshotCallbacks.set(id, {
937
+ resolve: (url) => { clearTimeout(timer); resolve(url); },
938
+ reject: (err) => { clearTimeout(timer); reject(err); },
939
+ });
940
+ browserWs.send(JSON.stringify({ type: "screenshot_request", id }));
941
+ });
942
+ res.json({ dataUrl });
943
+ }
944
+ catch (err) {
945
+ res.status(500).json({ error: toErrorMessage(err) });
946
+ }
947
+ });
948
+ // Called by WebSocket handler when browser sends screenshot_response
949
+ function handleScreenshotResponse(msg) {
950
+ const cb = screenshotCallbacks.get(msg.id);
951
+ if (!cb)
952
+ return;
953
+ screenshotCallbacks.delete(msg.id);
954
+ if (msg.error) {
955
+ cb.reject(new Error(msg.error));
956
+ }
957
+ else if (msg.dataUrl) {
958
+ cb.resolve(msg.dataUrl);
959
+ }
960
+ else {
961
+ cb.reject(new Error("Empty screenshot response"));
962
+ }
963
+ }
906
964
  // ── Agent context ──────────────────────────────────────────
907
965
  app.get("/agent-context", (req, res) => {
908
966
  const rawSince = queryInt(req.query.since);
@@ -1219,6 +1277,9 @@ export async function startServer(config) {
1219
1277
  }
1220
1278
  }
1221
1279
  }
1280
+ if (msg.type === "screenshot_response") {
1281
+ handleScreenshotResponse(msg);
1282
+ }
1222
1283
  }
1223
1284
  catch (err) {
1224
1285
  if (!(err instanceof SyntaxError)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibevibes/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server + runtime engine for vibevibes experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",