@vibebrowser/cli 0.2.8
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/README.md +13 -0
- package/dist/browser-cli.js +1881 -0
- package/dist/browser-main.js +11 -0
- package/dist/connection.js +536 -0
- package/dist/devtools-fallback.js +172 -0
- package/dist/relay-daemon.js +13 -0
- package/dist/relay.js +813 -0
- package/dist/types.js +22 -0
- package/dist/version.js +15 -0
- package/package.json +56 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
7
|
+
const TOOLS_REFRESH_TIMEOUT_MS = 6_000;
|
|
8
|
+
const TOOL_CALL_TIMEOUT_MS = 30_000;
|
|
9
|
+
const DEVTOOLS_UNAVAILABLE_PREFIX = 'chrome-devtools backend unavailable';
|
|
10
|
+
const DEVTOOLS_NOT_INSTALLED_MESSAGE = `${DEVTOOLS_UNAVAILABLE_PREFIX}: chrome-devtools-mcp is not installed`;
|
|
11
|
+
function normalizeToolName(value) {
|
|
12
|
+
return value.replace(/[-\s]/g, '_').toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
function toToolDefinition(input) {
|
|
15
|
+
return {
|
|
16
|
+
name: input.name,
|
|
17
|
+
description: input.description ?? '',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: input.inputSchema.properties,
|
|
21
|
+
required: input.inputSchema.required,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export class DevtoolsFallbackConnection extends EventEmitter {
|
|
26
|
+
debug;
|
|
27
|
+
client = null;
|
|
28
|
+
transport = null;
|
|
29
|
+
tools = [];
|
|
30
|
+
available = false;
|
|
31
|
+
unavailableReason;
|
|
32
|
+
constructor(debug) {
|
|
33
|
+
super();
|
|
34
|
+
this.debug = debug;
|
|
35
|
+
}
|
|
36
|
+
async start() {
|
|
37
|
+
const binaryPath = this.resolveBinaryPath();
|
|
38
|
+
if (!binaryPath) {
|
|
39
|
+
this.unavailableReason = DEVTOOLS_NOT_INSTALLED_MESSAGE;
|
|
40
|
+
this.log(this.unavailableReason);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const transport = new StdioClientTransport({
|
|
44
|
+
command: process.execPath,
|
|
45
|
+
args: [binaryPath, '--autoConnect'],
|
|
46
|
+
stderr: this.debug ? 'inherit' : 'pipe',
|
|
47
|
+
});
|
|
48
|
+
const client = new Client({
|
|
49
|
+
name: 'vibebrowser-mcp-devtools-fallback',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
}, { capabilities: {} });
|
|
52
|
+
try {
|
|
53
|
+
await client.connect(transport, { timeout: TOOLS_REFRESH_TIMEOUT_MS });
|
|
54
|
+
this.client = client;
|
|
55
|
+
this.transport = transport;
|
|
56
|
+
this.available = true;
|
|
57
|
+
this.unavailableReason = undefined;
|
|
58
|
+
await this.refreshTools(TOOLS_REFRESH_TIMEOUT_MS);
|
|
59
|
+
this.emit('connected');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
this.unavailableReason = `${DEVTOOLS_UNAVAILABLE_PREFIX}: ${message}`;
|
|
64
|
+
this.log(this.unavailableReason);
|
|
65
|
+
this.available = false;
|
|
66
|
+
this.client = null;
|
|
67
|
+
this.transport = null;
|
|
68
|
+
this.tools = [];
|
|
69
|
+
this.emit('unavailable', this.unavailableReason);
|
|
70
|
+
try {
|
|
71
|
+
await transport.close();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore cleanup errors
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async stop() {
|
|
79
|
+
this.available = false;
|
|
80
|
+
this.tools = [];
|
|
81
|
+
if (this.client) {
|
|
82
|
+
try {
|
|
83
|
+
await this.client.close();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// ignore shutdown errors
|
|
87
|
+
}
|
|
88
|
+
this.client = null;
|
|
89
|
+
}
|
|
90
|
+
if (this.transport) {
|
|
91
|
+
try {
|
|
92
|
+
await this.transport.close();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// ignore shutdown errors
|
|
96
|
+
}
|
|
97
|
+
this.transport = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async refreshTools(timeoutMs = TOOLS_REFRESH_TIMEOUT_MS) {
|
|
101
|
+
if (!this.client || !this.available) {
|
|
102
|
+
return this.tools;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const listed = await this.client.listTools(undefined, { timeout: timeoutMs });
|
|
106
|
+
const nextTools = listed.tools.map(toToolDefinition);
|
|
107
|
+
const previousNames = this.tools.map((tool) => normalizeToolName(tool.name)).join(',');
|
|
108
|
+
const nextNames = nextTools.map((tool) => normalizeToolName(tool.name)).join(',');
|
|
109
|
+
this.tools = nextTools;
|
|
110
|
+
if (previousNames !== nextNames) {
|
|
111
|
+
this.emit('tools_updated', this.tools);
|
|
112
|
+
}
|
|
113
|
+
return this.tools;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
this.log(`Unable to refresh chrome-devtools tools: ${message}`);
|
|
118
|
+
return this.tools;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
getTools() {
|
|
122
|
+
return this.tools;
|
|
123
|
+
}
|
|
124
|
+
hasTool(name) {
|
|
125
|
+
const needle = normalizeToolName(name);
|
|
126
|
+
return this.tools.some((tool) => normalizeToolName(tool.name) === needle);
|
|
127
|
+
}
|
|
128
|
+
async callTool(name, args, timeoutMs = TOOL_CALL_TIMEOUT_MS) {
|
|
129
|
+
if (!this.client || !this.available) {
|
|
130
|
+
throw new Error(this.unavailableReason || DEVTOOLS_UNAVAILABLE_PREFIX);
|
|
131
|
+
}
|
|
132
|
+
const result = await this.client.callTool({ name, arguments: args }, undefined, { timeout: timeoutMs });
|
|
133
|
+
const content = Array.isArray(result.content)
|
|
134
|
+
? result.content
|
|
135
|
+
: [{ type: 'text', text: JSON.stringify(result) }];
|
|
136
|
+
const isError = typeof result.isError === 'boolean' ? result.isError : false;
|
|
137
|
+
return {
|
|
138
|
+
success: !isError,
|
|
139
|
+
isError,
|
|
140
|
+
content,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
isAvailable() {
|
|
144
|
+
return this.available;
|
|
145
|
+
}
|
|
146
|
+
getUnavailableReason() {
|
|
147
|
+
return this.unavailableReason;
|
|
148
|
+
}
|
|
149
|
+
resolveBinaryPath() {
|
|
150
|
+
try {
|
|
151
|
+
const require = createRequire(import.meta.url);
|
|
152
|
+
const packageJsonPath = require.resolve('chrome-devtools-mcp/package.json');
|
|
153
|
+
const metadata = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
154
|
+
const bin = typeof metadata.bin === 'string'
|
|
155
|
+
? metadata.bin
|
|
156
|
+
: metadata.bin?.['chrome-devtools-mcp'] ?? metadata.bin?.['chrome-devtools'];
|
|
157
|
+
if (!bin) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return resolve(dirname(packageJsonPath), bin);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
log(message) {
|
|
167
|
+
if (this.debug) {
|
|
168
|
+
console.error(`[vibebrowser-mcp] ${message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=devtools-fallback.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Vibe MCP Relay Daemon Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This script is spawned as a detached daemon process.
|
|
6
|
+
*/
|
|
7
|
+
import { startRelayDaemon } from './relay.js';
|
|
8
|
+
const debug = process.argv.includes('--debug');
|
|
9
|
+
startRelayDaemon(debug).catch((error) => {
|
|
10
|
+
console.error(`[relay] Fatal error: ${error.message}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=relay-daemon.js.map
|