@kiro-paradigm/browser 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) 2025 Frank Ren
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,56 @@
1
+ # Browser Power for Kiro
2
+
3
+ A [Kiro](https://kiro.dev) power providing browser automation with a CDP fallback MCP server.
4
+
5
+ ## What's Included
6
+
7
+ | Component | Purpose |
8
+ |-----------|---------|
9
+ | `.kiro/powers/browser/` | Kiro power (Playwright + CDP + Chrome DevTools MCP servers) |
10
+ | `src/` | CDP fallback MCP server (`@renfeng/kiro-browser-alternative`) |
11
+
12
+ ## MCP Servers
13
+
14
+ - **Playwright** (enabled) — Full browser automation via `@playwright/mcp`
15
+ - **alternative** (enabled) — Fallback for when Chrome is already running with the same user data directory
16
+ - **Chrome DevTools** (disabled) — Alternative via `chrome-devtools-mcp`
17
+
18
+ ## Quick Start
19
+
20
+ ### As a Kiro Power
21
+
22
+ 1. Clone this repo
23
+ 2. In Kiro, open Command Palette → "Powers" → "Add power from Local Path"
24
+ 3. Select the `.kiro/powers/browser` directory
25
+
26
+ ### As a Workspace Root
27
+
28
+ Add to your `.code-workspace`:
29
+
30
+ ```json
31
+ { "name": "browser", "path": "../browser-kirospace" }
32
+ ```
33
+
34
+ The power at `.kiro/powers/browser/` is auto-discovered.
35
+
36
+ ## CDP Fallback
37
+
38
+ When Playwright fails with "Opening in existing browser session", the `alternative` server auto-discovers the running Chrome's CDP port and connects directly. Tools:
39
+
40
+ - `cdp_list_tabs` — List open tabs
41
+ - `cdp_navigate` — Navigate a tab to a URL
42
+ - `cdp_screenshot` — Capture viewport as PNG
43
+ - `cdp_evaluate` — Run JavaScript in a tab
44
+ - `cdp_reload` — Reload a tab
45
+
46
+ ## Development
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build
51
+ npm test
52
+ ```
53
+
54
+ ## License
55
+
56
+ MIT
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CDP Client — low-level Chrome DevTools Protocol communication.
3
+ *
4
+ * Discovers the CDP port from running Chrome processes, lists tabs,
5
+ * and sends CDP commands over websockets.
6
+ */
7
+ interface CdpTab {
8
+ id: string;
9
+ type: string;
10
+ title?: string;
11
+ url?: string;
12
+ webSocketDebuggerUrl?: string;
13
+ }
14
+ interface CdpResponse {
15
+ id?: number;
16
+ result?: Record<string, unknown>;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ };
21
+ }
22
+ export interface CdpEvalResult {
23
+ type?: string;
24
+ subtype?: string;
25
+ value?: unknown;
26
+ description?: string;
27
+ }
28
+ export declare class CdpClient {
29
+ private cachedPort;
30
+ private readonly cwd;
31
+ constructor(cwd?: string);
32
+ /**
33
+ * Auto-discover the CDP port from running Chrome processes.
34
+ *
35
+ * Convention: looks for a Chrome process whose --user-data-dir ends with
36
+ * `.playwright-data` and matches the working directory of this process.
37
+ * This ensures the CDP fallback connects to the same Chrome instance that
38
+ * Playwright launched for the current workspace.
39
+ *
40
+ * Falls back to the first discovered port if no workspace match is found.
41
+ */
42
+ discoverPort(): Promise<number>;
43
+ /**
44
+ * List open tabs on the given CDP port.
45
+ */
46
+ listTabs(port: number): Promise<CdpTab[]>;
47
+ /**
48
+ * Get the websocket URL for a tab by index.
49
+ */
50
+ getTabWsUrl(port: number, tabIndex: number): Promise<string>;
51
+ /**
52
+ * Send a CDP command and return the response.
53
+ */
54
+ send(wsUrl: string, method: string, params?: Record<string, unknown>, timeout?: number): Promise<CdpResponse>;
55
+ private httpGet;
56
+ }
57
+ export {};
@@ -0,0 +1,150 @@
1
+ /**
2
+ * CDP Client — low-level Chrome DevTools Protocol communication.
3
+ *
4
+ * Discovers the CDP port from running Chrome processes, lists tabs,
5
+ * and sends CDP commands over websockets.
6
+ */
7
+ import { execFile } from 'node:child_process';
8
+ import { promisify } from 'node:util';
9
+ import http from 'node:http';
10
+ import WebSocket from 'ws';
11
+ const execFileAsync = promisify(execFile);
12
+ export class CdpClient {
13
+ cachedPort = null;
14
+ cwd;
15
+ constructor(cwd) {
16
+ this.cwd = cwd ?? process.cwd();
17
+ }
18
+ /**
19
+ * Auto-discover the CDP port from running Chrome processes.
20
+ *
21
+ * Convention: looks for a Chrome process whose --user-data-dir ends with
22
+ * `.playwright-data` and matches the working directory of this process.
23
+ * This ensures the CDP fallback connects to the same Chrome instance that
24
+ * Playwright launched for the current workspace.
25
+ *
26
+ * Falls back to the first discovered port if no workspace match is found.
27
+ */
28
+ async discoverPort() {
29
+ if (this.cachedPort) {
30
+ try {
31
+ await this.httpGet(`http://localhost:${this.cachedPort}/json/version`);
32
+ return this.cachedPort;
33
+ }
34
+ catch {
35
+ this.cachedPort = null;
36
+ }
37
+ }
38
+ try {
39
+ // Convention: match the .playwright-data dir under the current working directory
40
+ const expectedDataDir = `${this.cwd}/.playwright-data`;
41
+ // Try workspace-specific match first (own try/catch so failure falls through to generic)
42
+ try {
43
+ // Run ps aux and match in Node.js to avoid shell escaping and prefix-collision issues.
44
+ // grep -F with env var narrows to candidate lines; JS does exact boundary matching.
45
+ const { stdout: specificOut } = await execFileAsync('bash', [
46
+ '-c',
47
+ `ps aux | grep -v grep | grep -F "user-data-dir=$EXPECTED_DIR"`,
48
+ ], { env: { ...process.env, EXPECTED_DIR: expectedDataDir } });
49
+ // Exact boundary match: the path must be followed by a space, quote, or end-of-line
50
+ const portPattern = /remote-debugging-port=(\d+)/;
51
+ const exactPattern = `user-data-dir=${expectedDataDir}`;
52
+ const matchedLine = specificOut.split('\n').find(line => {
53
+ const idx = line.indexOf(exactPattern);
54
+ if (idx === -1)
55
+ return false;
56
+ const afterChar = line[idx + exactPattern.length];
57
+ return afterChar === undefined || afterChar === ' ' || afterChar === '"' || afterChar === "'" || afterChar === '/';
58
+ });
59
+ const specificMatch = matchedLine?.match(portPattern);
60
+ if (specificMatch) {
61
+ const port = parseInt(specificMatch[1], 10);
62
+ await this.httpGet(`http://localhost:${port}/json/version`);
63
+ this.cachedPort = port;
64
+ return port;
65
+ }
66
+ }
67
+ catch {
68
+ // Workspace-specific discovery failed — fall through to generic.
69
+ // Intentionally silent: the generic path will either succeed or the
70
+ // outer catch will surface a meaningful error message.
71
+ }
72
+ // Fall back to any Chrome with remote-debugging-port
73
+ const { stdout } = await execFileAsync('bash', [
74
+ '-c',
75
+ 'ps aux | grep -v grep | grep -o "remote-debugging-port=[0-9]*" | head -1',
76
+ ]);
77
+ const match = stdout.trim().match(/remote-debugging-port=(\d+)/);
78
+ if (match) {
79
+ const port = parseInt(match[1], 10);
80
+ await this.httpGet(`http://localhost:${port}/json/version`);
81
+ this.cachedPort = port;
82
+ return port;
83
+ }
84
+ }
85
+ catch {
86
+ // fall through
87
+ }
88
+ throw new Error('Cannot find a running Chrome with --remote-debugging-port. ' +
89
+ 'Start Chrome with --remote-debugging-port=PORT or let Playwright MCP launch it first.');
90
+ }
91
+ /**
92
+ * List open tabs on the given CDP port.
93
+ */
94
+ async listTabs(port) {
95
+ const body = await this.httpGet(`http://localhost:${port}/json/list`);
96
+ return JSON.parse(body);
97
+ }
98
+ /**
99
+ * Get the websocket URL for a tab by index.
100
+ */
101
+ async getTabWsUrl(port, tabIndex) {
102
+ const tabs = await this.listTabs(port);
103
+ if (tabIndex < 0 || tabIndex >= tabs.length) {
104
+ throw new Error(`Tab index ${tabIndex} out of range (0-${tabs.length - 1})`);
105
+ }
106
+ const wsUrl = tabs[tabIndex].webSocketDebuggerUrl;
107
+ if (!wsUrl) {
108
+ throw new Error(`Tab ${tabIndex} has no webSocketDebuggerUrl (type: ${tabs[tabIndex].type})`);
109
+ }
110
+ return wsUrl;
111
+ }
112
+ /**
113
+ * Send a CDP command and return the response.
114
+ */
115
+ async send(wsUrl, method, params = {}, timeout = 30000) {
116
+ return new Promise((resolve, reject) => {
117
+ const ws = new WebSocket(wsUrl, { maxPayload: 50 * 1024 * 1024 });
118
+ const timer = setTimeout(() => {
119
+ ws.close();
120
+ reject(new Error(`Timeout waiting for CDP response to ${method}`));
121
+ }, timeout);
122
+ ws.on('open', () => {
123
+ ws.send(JSON.stringify({ id: 1, method, params }));
124
+ });
125
+ ws.on('message', (data) => {
126
+ const msg = JSON.parse(data.toString());
127
+ if (msg.id === 1) {
128
+ clearTimeout(timer);
129
+ ws.close();
130
+ resolve(msg);
131
+ }
132
+ });
133
+ ws.on('error', (err) => {
134
+ clearTimeout(timer);
135
+ reject(err);
136
+ });
137
+ });
138
+ }
139
+ httpGet(url) {
140
+ return new Promise((resolve, reject) => {
141
+ http.get(url, { timeout: 5000 }, (res) => {
142
+ let body = '';
143
+ res.on('data', (chunk) => { body += chunk.toString(); });
144
+ res.on('end', () => resolve(body));
145
+ res.on('error', reject);
146
+ }).on('error', reject);
147
+ });
148
+ }
149
+ }
150
+ //# sourceMappingURL=cdp-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp-client.js","sourceRoot":"","sources":["../src/cdp-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CDP Browser MCP Server — connects to an already-running Chrome instance
4
+ * via Chrome DevTools Protocol when Playwright MCP cannot launch.
5
+ *
6
+ * Auto-discovers the CDP port from running Chrome processes.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CDP Browser MCP Server — connects to an already-running Chrome instance
4
+ * via Chrome DevTools Protocol when Playwright MCP cannot launch.
5
+ *
6
+ * Auto-discovers the CDP port from running Chrome processes.
7
+ */
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { z } from 'zod';
11
+ import { CdpClient } from './cdp-client.js';
12
+ const server = new McpServer({
13
+ name: 'alternative',
14
+ version: '0.1.0',
15
+ });
16
+ // Workspace-specific port discovery relies on process.cwd() being the workspace
17
+ // folder at startup. Kiro sets cwd to the workspace folder when launching MCP
18
+ // servers, so the default (no explicit cwd) is correct for production use.
19
+ const cdp = new CdpClient();
20
+ // --- Tool: cdp_list_tabs ---
21
+ server.tool('cdp_list_tabs', 'List open browser tabs. Auto-discovers the CDP port from running Chrome processes.', { port: z.number().optional().describe('CDP port (auto-detected if omitted)') }, async ({ port }) => {
22
+ const resolvedPort = port ?? await cdp.discoverPort();
23
+ const tabs = await cdp.listTabs(resolvedPort);
24
+ const pages = tabs
25
+ .filter((t) => t.type === 'page')
26
+ .map((t, i) => `${i}: ${t.title ?? '(no title)'}\n ${t.url ?? ''}`);
27
+ return { content: [{ type: 'text', text: pages.join('\n\n') || '(no page tabs open)' }] };
28
+ });
29
+ // --- Tool: cdp_navigate ---
30
+ server.tool('cdp_navigate', 'Navigate a browser tab to a URL.', {
31
+ tab: z.number().describe('Tab index from cdp_list_tabs'),
32
+ url: z.string().describe('URL to navigate to'),
33
+ wait: z.number().optional().default(3).describe('Seconds to wait after navigation (default: 3)'),
34
+ port: z.number().optional().describe('CDP port (auto-detected if omitted)'),
35
+ }, async ({ tab, url, wait, port }) => {
36
+ const resolvedPort = port ?? await cdp.discoverPort();
37
+ const wsUrl = await cdp.getTabWsUrl(resolvedPort, tab);
38
+ const result = await cdp.send(wsUrl, 'Page.navigate', { url });
39
+ if (result.error) {
40
+ return { content: [{ type: 'text', text: `Error: ${JSON.stringify(result.error)}` }], isError: true };
41
+ }
42
+ if (wait > 0)
43
+ await sleep(wait * 1000);
44
+ return { content: [{ type: 'text', text: `Navigated to ${url}` }] };
45
+ });
46
+ // --- Tool: cdp_screenshot ---
47
+ server.tool('cdp_screenshot', 'Take a screenshot of a browser tab. Returns the image as base64-encoded PNG.', {
48
+ tab: z.number().describe('Tab index from cdp_list_tabs'),
49
+ wait: z.number().optional().default(0).describe('Seconds to wait before screenshot (default: 0)'),
50
+ port: z.number().optional().describe('CDP port (auto-detected if omitted)'),
51
+ }, async ({ tab, wait, port }) => {
52
+ const resolvedPort = port ?? await cdp.discoverPort();
53
+ const wsUrl = await cdp.getTabWsUrl(resolvedPort, tab);
54
+ if (wait > 0)
55
+ await sleep(wait * 1000);
56
+ const result = await cdp.send(wsUrl, 'Page.captureScreenshot', { format: 'png' });
57
+ if (result.error) {
58
+ return { content: [{ type: 'text', text: `Error: ${JSON.stringify(result.error)}` }], isError: true };
59
+ }
60
+ const data = result.result?.data;
61
+ if (typeof data !== 'string') {
62
+ return { content: [{ type: 'text', text: 'Error: no screenshot data returned' }], isError: true };
63
+ }
64
+ return {
65
+ content: [{
66
+ type: 'image',
67
+ data,
68
+ mimeType: 'image/png',
69
+ }],
70
+ };
71
+ });
72
+ // --- Tool: cdp_evaluate ---
73
+ server.tool('cdp_evaluate', 'Evaluate a JavaScript expression in a browser tab and return the result.', {
74
+ tab: z.number().describe('Tab index from cdp_list_tabs'),
75
+ expression: z.string().describe('JavaScript expression to evaluate'),
76
+ port: z.number().optional().describe('CDP port (auto-detected if omitted)'),
77
+ }, async ({ tab, expression, port }) => {
78
+ const resolvedPort = port ?? await cdp.discoverPort();
79
+ const wsUrl = await cdp.getTabWsUrl(resolvedPort, tab);
80
+ const result = await cdp.send(wsUrl, 'Runtime.evaluate', {
81
+ expression,
82
+ returnByValue: true,
83
+ });
84
+ if (result.error) {
85
+ return { content: [{ type: 'text', text: `Error: ${JSON.stringify(result.error)}` }], isError: true };
86
+ }
87
+ const evalResult = result.result?.result;
88
+ if (!evalResult) {
89
+ return { content: [{ type: 'text', text: '(no result)' }] };
90
+ }
91
+ if (evalResult.type === 'undefined') {
92
+ return { content: [{ type: 'text', text: '(undefined)' }] };
93
+ }
94
+ if (evalResult.subtype === 'error') {
95
+ return { content: [{ type: 'text', text: `Error: ${evalResult.description ?? evalResult.value}` }], isError: true };
96
+ }
97
+ const value = evalResult.value;
98
+ const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
99
+ return { content: [{ type: 'text', text }] };
100
+ });
101
+ // --- Tool: cdp_reload ---
102
+ server.tool('cdp_reload', 'Reload a browser tab.', {
103
+ tab: z.number().describe('Tab index from cdp_list_tabs'),
104
+ wait: z.number().optional().default(3).describe('Seconds to wait after reload (default: 3)'),
105
+ port: z.number().optional().describe('CDP port (auto-detected if omitted)'),
106
+ }, async ({ tab, wait, port }) => {
107
+ const resolvedPort = port ?? await cdp.discoverPort();
108
+ const wsUrl = await cdp.getTabWsUrl(resolvedPort, tab);
109
+ await cdp.send(wsUrl, 'Page.reload', {});
110
+ if (wait > 0)
111
+ await sleep(wait * 1000);
112
+ return { content: [{ type: 'text', text: 'Page reloaded' }] };
113
+ });
114
+ function sleep(ms) {
115
+ return new Promise(resolve => setTimeout(resolve, ms));
116
+ }
117
+ // --- Start ---
118
+ async function main() {
119
+ const transport = new StdioServerTransport();
120
+ await server.connect(transport);
121
+ }
122
+ main().catch((err) => {
123
+ console.error('Fatal:', err);
124
+ process.exit(1);
125
+ });
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@kiro-paradigm/browser",
3
+ "version": "0.1.0",
4
+ "description": "MCP server providing Chrome DevTools Protocol (CDP) browser automation — connects to an already-running Chrome instance when Playwright MCP cannot launch",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "browser-cdp-mcp": "dist/index.js"
9
+ },
10
+ "keywords": [
11
+ "mcp",
12
+ "model-context-protocol",
13
+ "browser",
14
+ "chrome",
15
+ "cdp",
16
+ "devtools",
17
+ "automation",
18
+ "kiro"
19
+ ],
20
+ "author": "Frank Ren",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://gitlab.com/kiro-paradigm/browser-kirospace.git"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "registry": "https://registry.npmjs.org"
31
+ },
32
+ "files": [
33
+ "dist/",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "prepublishOnly": "npm run build",
40
+ "type-check": "tsc --noEmit",
41
+ "test": "vitest --run",
42
+ "test:watch": "vitest"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.25.1",
46
+ "ws": "^8.18.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.0.0",
50
+ "@types/ws": "^8.5.0",
51
+ "typescript": "^5.0.0",
52
+ "vitest": "^4.1.0"
53
+ }
54
+ }