viewpo-mcp 0.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 Little Bear Apps
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/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # viewpo-mcp
2
+
3
+ MCP server that gives AI coding assistants **eyes into responsive viewport rendering** via the [Viewpo](https://viewpo.app) macOS app.
4
+
5
+ Capture multi-viewport screenshots, extract DOM layout trees, and compare responsive behaviour across breakpoints — all from your AI assistant.
6
+
7
+ ## What it does
8
+
9
+ AI coding assistants (Claude Code, Cursor, Windsurf, etc.) are **blind** when working on frontend UI. They write CSS and HTML but can't see the visual result. This creates a slow feedback loop:
10
+
11
+ > AI writes code → you check → you describe what's wrong → AI fixes → repeat
12
+
13
+ **viewpo-mcp** closes that loop. The AI can now:
14
+
15
+ 1. **Screenshot** a URL at multiple viewport widths simultaneously
16
+ 2. **Extract** the DOM layout tree with bounding rects and computed styles
17
+ 3. **Compare** layouts at two different viewport widths to find responsive issues
18
+
19
+ ## Requirements
20
+
21
+ - **[Viewpo](https://viewpo.app) macOS app** with MCP Bridge enabled (Settings → MCP Bridge → Start)
22
+ - **Node.js 20+**
23
+
24
+ ## Setup
25
+
26
+ ### 1. Install
27
+
28
+ ```bash
29
+ npm install -g viewpo-mcp
30
+ ```
31
+
32
+ Or run directly with npx (no install needed):
33
+
34
+ ```bash
35
+ npx viewpo-mcp
36
+ ```
37
+
38
+ ### 2. Enable the MCP Bridge in Viewpo
39
+
40
+ 1. Open Viewpo on your Mac
41
+ 2. Go to **Settings → MCP Bridge**
42
+ 3. Click **Start** to enable the bridge server
43
+ 4. Copy the **auth token** (click the copy button next to the token)
44
+
45
+ ### 3. Configure your AI assistant
46
+
47
+ #### Claude Code
48
+
49
+ Add to your MCP config (`~/.claude/mcp.json` or project `.mcp.json`):
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "viewpo": {
55
+ "command": "npx",
56
+ "args": ["-y", "viewpo-mcp"],
57
+ "env": {
58
+ "VIEWPO_AUTH_TOKEN": "<paste token from step 2>"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ #### Cursor
66
+
67
+ Add to your Cursor MCP settings (`.cursor/mcp.json`):
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "viewpo": {
73
+ "command": "npx",
74
+ "args": ["-y", "viewpo-mcp"],
75
+ "env": {
76
+ "VIEWPO_AUTH_TOKEN": "<paste token from step 2>"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ #### Other MCP-compatible assistants
84
+
85
+ Any assistant supporting the [Model Context Protocol](https://modelcontextprotocol.io) can use viewpo-mcp. Set the command to `npx -y viewpo-mcp` and provide the `VIEWPO_AUTH_TOKEN` environment variable.
86
+
87
+ ## Tools
88
+
89
+ ### `viewpo_screenshot`
90
+
91
+ Capture screenshots of a URL at one or more viewport widths. Returns base64 JPEG images.
92
+
93
+ ```
94
+ url: "https://example.com" (required)
95
+ viewports: [{ width: 375, name: "phone" }, (optional, defaults to 1920px desktop)
96
+ { width: 1920, name: "desktop" }]
97
+ ```
98
+
99
+ Common widths: **375** (phone), **820** (tablet), **1920** (desktop).
100
+
101
+ ### `viewpo_get_layout_map`
102
+
103
+ Extract the DOM layout tree at a given viewport width. Returns element hierarchy with tags, classes, bounding rects, and computed CSS styles.
104
+
105
+ ```
106
+ url: "https://example.com" (required)
107
+ viewport: 1920 (optional, default 1920)
108
+ selector: ".main-content" (optional, scope to subtree)
109
+ ```
110
+
111
+ ### `viewpo_compare_viewports`
112
+
113
+ Compare the layout of a URL at two different viewport widths. Returns elements whose size or CSS styles differ between the two viewports.
114
+
115
+ ```
116
+ url: "https://example.com" (required)
117
+ viewport_a: 375 (required)
118
+ viewport_b: 1920 (required)
119
+ selector: ".hero" (optional, scope to subtree)
120
+ ```
121
+
122
+ ## Environment variables
123
+
124
+ | Variable | Required | Default | Description |
125
+ |----------|----------|---------|-------------|
126
+ | `VIEWPO_AUTH_TOKEN` | Yes | — | Bearer token from Viewpo Settings &rarr; MCP Bridge |
127
+ | `VIEWPO_PORT` | No | `9847` | Port the Viewpo bridge server listens on |
128
+
129
+ ## How it works
130
+
131
+ ```
132
+ AI Assistant (Claude Code / Cursor / Windsurf)
133
+ | stdio (MCP protocol)
134
+ v
135
+ viewpo-mcp (this package)
136
+ | HTTP localhost:9847
137
+ v
138
+ Viewpo macOS app (NWListener)
139
+ | WKWebView rendering
140
+ v
141
+ Headless browser pool (off-screen, up to 4 concurrent pages)
142
+ ```
143
+
144
+ The Viewpo app runs a local HTTP server on `localhost:9847`. This MCP server translates tool calls into HTTP requests to that bridge. The app loads pages in headless WKWebView instances with real CSS viewport simulation — media queries fire at the target width, not the physical screen width.
145
+
146
+ **Key advantage**: WKWebView bypasses X-Frame-Options entirely. Any website loads, regardless of security headers that would block iframe-based tools.
147
+
148
+ ## Example workflow
149
+
150
+ ```
151
+ You: "The hero section looks broken on mobile"
152
+
153
+ AI: 1. viewpo_screenshot("https://preview.example.com", [{width: 375}, {width: 1920}])
154
+ → Sees the layout at both sizes
155
+ 2. viewpo_compare_viewports("https://preview.example.com", 375, 1920)
156
+ → Gets structured diff: ".hero img" is 1200px wide on mobile (overflowing)
157
+ 3. Fixes the CSS
158
+ 4. viewpo_screenshot("https://preview.example.com", [{width: 375}])
159
+ → Verifies the fix
160
+ ```
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ git clone https://github.com/littlebearapps/viewpo-mcp.git
166
+ cd viewpo-mcp
167
+ npm install
168
+ npm run build # compile TypeScript
169
+ npm run dev # watch mode
170
+ ```
171
+
172
+ ## Licence
173
+
174
+ MIT - see [LICENSE](LICENSE).
175
+
176
+ ## Links
177
+
178
+ - [Viewpo app](https://viewpo.app) - The macOS/iOS app
179
+ - [Model Context Protocol](https://modelcontextprotocol.io) - MCP specification
180
+ - [GitHub](https://github.com/littlebearapps/viewpo-mcp) - Source code
@@ -0,0 +1,18 @@
1
+ /**
2
+ * HTTP client for the Viewpo bridge API.
3
+ * Communicates with the Viewpo macOS app's NWListener on localhost.
4
+ */
5
+ import type { ScreenshotRequest, ScreenshotResponse, LayoutMapRequest, LayoutMapResponse, CompareRequest, CompareResponse, HealthResponse } from "./types.js";
6
+ export declare class ViewpoBridgeClient {
7
+ private readonly baseURL;
8
+ private readonly authToken;
9
+ constructor(port?: number, authToken?: string);
10
+ health(): Promise<HealthResponse>;
11
+ screenshot(request: ScreenshotRequest): Promise<ScreenshotResponse>;
12
+ layoutMap(request: LayoutMapRequest): Promise<LayoutMapResponse>;
13
+ compare(request: CompareRequest): Promise<CompareResponse>;
14
+ private get;
15
+ private post;
16
+ private headers;
17
+ private handleResponse;
18
+ }
package/dist/client.js ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * HTTP client for the Viewpo bridge API.
3
+ * Communicates with the Viewpo macOS app's NWListener on localhost.
4
+ */
5
+ export class ViewpoBridgeClient {
6
+ baseURL;
7
+ authToken;
8
+ constructor(port = 9847, authToken = "") {
9
+ this.baseURL = `http://127.0.0.1:${port}`;
10
+ this.authToken = authToken;
11
+ }
12
+ async health() {
13
+ return this.get("/health");
14
+ }
15
+ async screenshot(request) {
16
+ return this.post("/screenshot", request);
17
+ }
18
+ async layoutMap(request) {
19
+ return this.post("/layout", request);
20
+ }
21
+ async compare(request) {
22
+ return this.post("/compare", request);
23
+ }
24
+ async get(path) {
25
+ const response = await fetch(`${this.baseURL}${path}`, {
26
+ method: "GET",
27
+ headers: this.headers(false),
28
+ });
29
+ return this.handleResponse(response);
30
+ }
31
+ async post(path, body) {
32
+ const response = await fetch(`${this.baseURL}${path}`, {
33
+ method: "POST",
34
+ headers: this.headers(true),
35
+ body: JSON.stringify(body),
36
+ });
37
+ return this.handleResponse(response);
38
+ }
39
+ headers(includeContentType) {
40
+ const h = {};
41
+ if (this.authToken) {
42
+ h["Authorization"] = `Bearer ${this.authToken}`;
43
+ }
44
+ if (includeContentType) {
45
+ h["Content-Type"] = "application/json";
46
+ }
47
+ return h;
48
+ }
49
+ async handleResponse(response) {
50
+ const text = await response.text();
51
+ if (!response.ok) {
52
+ let message = `HTTP ${response.status}`;
53
+ try {
54
+ const err = JSON.parse(text);
55
+ message = err.error || message;
56
+ }
57
+ catch {
58
+ if (text)
59
+ message = text;
60
+ }
61
+ throw new Error(`Viewpo bridge error (${response.status}): ${message}`);
62
+ }
63
+ try {
64
+ return JSON.parse(text);
65
+ }
66
+ catch {
67
+ throw new Error(`Invalid JSON response from Viewpo bridge: ${text.slice(0, 200)}`);
68
+ }
69
+ }
70
+ }
71
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Viewpo MCP Server
4
+ *
5
+ * Gives AI coding assistants visual inspection tools for responsive
6
+ * web development via the Viewpo macOS app.
7
+ *
8
+ * Transport: stdio (for Claude Code, Cursor, Windsurf, etc.)
9
+ * Communicates with: Viewpo macOS app on localhost:9847
10
+ *
11
+ * @see https://github.com/littlebearapps/viewpo-mcp
12
+ */
13
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Viewpo MCP Server
4
+ *
5
+ * Gives AI coding assistants visual inspection tools for responsive
6
+ * web development via the Viewpo macOS app.
7
+ *
8
+ * Transport: stdio (for Claude Code, Cursor, Windsurf, etc.)
9
+ * Communicates with: Viewpo macOS app on localhost:9847
10
+ *
11
+ * @see https://github.com/littlebearapps/viewpo-mcp
12
+ */
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z } from "zod";
16
+ import { ViewpoBridgeClient } from "./client.js";
17
+ // --- Configuration from environment ---
18
+ const port = parseInt(process.env.VIEWPO_PORT || "9847", 10);
19
+ const authToken = process.env.VIEWPO_AUTH_TOKEN || "";
20
+ if (!authToken) {
21
+ console.error("Warning: VIEWPO_AUTH_TOKEN not set. Copy the token from Viewpo > Settings > MCP Bridge.");
22
+ }
23
+ const client = new ViewpoBridgeClient(port, authToken);
24
+ // --- MCP Server ---
25
+ const server = new McpServer({
26
+ name: "viewpo",
27
+ version: "0.1.0",
28
+ });
29
+ // --- Tool: viewpo_screenshot ---
30
+ server.tool("viewpo_screenshot", "Capture screenshots of a URL at one or more viewport widths. Returns base64 JPEG images. Use this to SEE what a webpage looks like at different screen sizes.", {
31
+ url: z.string().url().describe("The URL to screenshot"),
32
+ viewports: z
33
+ .array(z.object({
34
+ width: z
35
+ .number()
36
+ .int()
37
+ .min(100)
38
+ .max(3840)
39
+ .describe("Viewport width in CSS pixels"),
40
+ name: z
41
+ .string()
42
+ .optional()
43
+ .describe('Label for this viewport (e.g. "phone", "desktop")'),
44
+ }))
45
+ .optional()
46
+ .describe("Viewports to capture. Defaults to desktop (1920px) if omitted. Common widths: 375 (phone), 820 (tablet), 1920 (desktop)."),
47
+ }, async ({ url, viewports }) => {
48
+ try {
49
+ const result = await client.screenshot({ url, viewports });
50
+ const content = result.viewports.flatMap((vp) => [
51
+ {
52
+ type: "text",
53
+ text: `**${vp.viewport}** (${vp.width}\u00d7${vp.height})`,
54
+ },
55
+ {
56
+ type: "image",
57
+ data: vp.image_base64,
58
+ mimeType: "image/jpeg",
59
+ },
60
+ ]);
61
+ return { content };
62
+ }
63
+ catch (error) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: `Screenshot failed: ${error instanceof Error ? error.message : String(error)}`,
69
+ },
70
+ ],
71
+ isError: true,
72
+ };
73
+ }
74
+ });
75
+ // --- Tool: viewpo_get_layout_map ---
76
+ server.tool("viewpo_get_layout_map", "Extract the DOM layout tree of a URL at a given viewport width. Returns element hierarchy with tags, classes, bounding rects, and computed CSS styles. Use this to understand page structure and find layout issues.", {
77
+ url: z.string().url().describe("The URL to inspect"),
78
+ viewport: z
79
+ .number()
80
+ .int()
81
+ .min(100)
82
+ .max(3840)
83
+ .optional()
84
+ .describe("Viewport width in CSS pixels (default: 1920)"),
85
+ selector: z
86
+ .string()
87
+ .optional()
88
+ .describe('CSS selector to scope the layout map to a subtree (e.g. ".main-content", "#hero")'),
89
+ }, async ({ url, viewport, selector }) => {
90
+ try {
91
+ const result = await client.layoutMap({ url, viewport, selector });
92
+ return {
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: JSON.stringify(result, null, 2),
97
+ },
98
+ ],
99
+ };
100
+ }
101
+ catch (error) {
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: `Layout map failed: ${error instanceof Error ? error.message : String(error)}`,
107
+ },
108
+ ],
109
+ isError: true,
110
+ };
111
+ }
112
+ });
113
+ // --- Tool: viewpo_compare_viewports ---
114
+ server.tool("viewpo_compare_viewports", "Compare the layout of a URL at two different viewport widths. Returns a list of elements whose size or CSS styles differ between the two viewports. Use this to find responsive design issues.", {
115
+ url: z.string().url().describe("The URL to compare"),
116
+ viewport_a: z
117
+ .number()
118
+ .int()
119
+ .min(100)
120
+ .max(3840)
121
+ .describe("First viewport width in CSS pixels (e.g. 375 for phone)"),
122
+ viewport_b: z
123
+ .number()
124
+ .int()
125
+ .min(100)
126
+ .max(3840)
127
+ .describe("Second viewport width in CSS pixels (e.g. 1920 for desktop)"),
128
+ selector: z
129
+ .string()
130
+ .optional()
131
+ .describe("CSS selector to scope comparison to a subtree"),
132
+ }, async ({ url, viewport_a, viewport_b, selector }) => {
133
+ try {
134
+ const result = await client.compare({
135
+ url,
136
+ viewport_a,
137
+ viewport_b,
138
+ selector,
139
+ });
140
+ const summary = result.differences.length === 0
141
+ ? "No layout differences found between the two viewports."
142
+ : `Found ${result.differences.length} difference(s) between ${result.viewport_a}px and ${result.viewport_b}px:`;
143
+ return {
144
+ content: [
145
+ {
146
+ type: "text",
147
+ text: `${summary}\n\n${JSON.stringify(result, null, 2)}`,
148
+ },
149
+ ],
150
+ };
151
+ }
152
+ catch (error) {
153
+ return {
154
+ content: [
155
+ {
156
+ type: "text",
157
+ text: `Compare failed: ${error instanceof Error ? error.message : String(error)}`,
158
+ },
159
+ ],
160
+ isError: true,
161
+ };
162
+ }
163
+ });
164
+ // --- Start ---
165
+ async function main() {
166
+ const transport = new StdioServerTransport();
167
+ await server.connect(transport);
168
+ }
169
+ main().catch((error) => {
170
+ console.error("Viewpo MCP server fatal error:", error);
171
+ process.exit(1);
172
+ });
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /** TypeScript interfaces matching the Viewpo macOS app bridge API. */
2
+ export interface ViewportSpec {
3
+ width: number;
4
+ name?: string;
5
+ }
6
+ export interface ScreenshotRequest {
7
+ url: string;
8
+ viewports?: ViewportSpec[];
9
+ }
10
+ export interface ViewportResult {
11
+ viewport: string;
12
+ width: number;
13
+ height: number;
14
+ image_base64: string;
15
+ }
16
+ export interface ScreenshotResponse {
17
+ viewports: ViewportResult[];
18
+ }
19
+ export interface LayoutMapRequest {
20
+ url: string;
21
+ viewport?: number;
22
+ selector?: string;
23
+ }
24
+ export interface ElementRect {
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ }
30
+ export interface LayoutElement {
31
+ tag: string;
32
+ id?: string;
33
+ classes?: string[];
34
+ rect: ElementRect;
35
+ styles: Record<string, string>;
36
+ children: LayoutElement[];
37
+ }
38
+ export interface LayoutMapResponse {
39
+ url: string;
40
+ viewport: number;
41
+ root: LayoutElement;
42
+ }
43
+ export interface CompareRequest {
44
+ url: string;
45
+ viewport_a: number;
46
+ viewport_b: number;
47
+ selector?: string;
48
+ }
49
+ export interface LayoutDifference {
50
+ selector: string;
51
+ tag: string;
52
+ property: string;
53
+ value_a: string;
54
+ value_b: string;
55
+ }
56
+ export interface CompareResponse {
57
+ url: string;
58
+ viewport_a: number;
59
+ viewport_b: number;
60
+ differences: LayoutDifference[];
61
+ }
62
+ export interface HealthResponse {
63
+ status: string;
64
+ version: string;
65
+ port: number;
66
+ }
67
+ export interface ErrorResponse {
68
+ error: string;
69
+ code?: number;
70
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** TypeScript interfaces matching the Viewpo macOS app bridge API. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "viewpo-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Viewpo — gives AI coding assistants eyes into responsive viewport rendering",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "viewpo-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "viewpo",
20
+ "viewport",
21
+ "responsive",
22
+ "screenshot",
23
+ "ai",
24
+ "claude",
25
+ "cursor",
26
+ "webview",
27
+ "frontend",
28
+ "testing"
29
+ ],
30
+ "author": "Little Bear Apps <hello@littlebearapps.com> (https://littlebearapps.com)",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/littlebearapps/viewpo-mcp.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/littlebearapps/viewpo-mcp/issues"
38
+ },
39
+ "homepage": "https://github.com/littlebearapps/viewpo-mcp#readme",
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.12.0",
42
+ "zod": "^3.22.0"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.7.0",
46
+ "@types/node": "^22.0.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=20"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "LICENSE",
54
+ "README.md"
55
+ ]
56
+ }