dev3000 0.0.49 → 0.0.51
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 +55 -6
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +54 -48
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +39 -33
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts +2 -0
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +212 -181
- package/dist/dev-environment.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/mcp-server/app/api/config/route.ts +7 -7
- package/mcp-server/app/api/logs/append/route.ts +59 -51
- package/mcp-server/app/api/logs/head/route.ts +22 -22
- package/mcp-server/app/api/logs/list/route.ts +39 -42
- package/mcp-server/app/api/logs/rotate/route.ts +28 -38
- package/mcp-server/app/api/logs/stream/route.ts +35 -35
- package/mcp-server/app/api/logs/tail/route.ts +22 -22
- package/mcp-server/app/api/mcp/[transport]/route.ts +189 -188
- package/mcp-server/app/api/replay/route.ts +217 -202
- package/mcp-server/app/layout.tsx +9 -8
- package/mcp-server/app/logs/LogsClient.test.ts +123 -99
- package/mcp-server/app/logs/LogsClient.tsx +724 -562
- package/mcp-server/app/logs/page.tsx +71 -72
- package/mcp-server/app/logs/utils.ts +99 -28
- package/mcp-server/app/page.tsx +10 -14
- package/mcp-server/app/replay/ReplayClient.tsx +120 -119
- package/mcp-server/app/replay/page.tsx +3 -3
- package/mcp-server/next.config.ts +2 -0
- package/mcp-server/package.json +5 -2
- package/mcp-server/pnpm-lock.yaml +37 -5
- package/mcp-server/tsconfig.json +4 -17
- package/package.json +16 -13
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { WebSocket } from "ws";
|
|
1
|
+
import { existsSync, readFileSync } from "fs"
|
|
2
|
+
import { createMcpHandler } from "mcp-handler"
|
|
3
|
+
import { WebSocket } from "ws"
|
|
4
|
+
import { z } from "zod"
|
|
6
5
|
|
|
7
|
-
const handler = createMcpHandler(
|
|
6
|
+
const handler = createMcpHandler(
|
|
7
|
+
(server) => {
|
|
8
8
|
// Healthcheck tool
|
|
9
9
|
server.tool(
|
|
10
10
|
"healthcheck",
|
|
@@ -20,9 +20,9 @@ const handler = createMcpHandler((server) => {
|
|
|
20
20
|
text: `✅ ${message} - Timestamp: ${new Date().toISOString()}`
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
|
-
}
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
|
-
)
|
|
25
|
+
)
|
|
26
26
|
|
|
27
27
|
// Tool to read consolidated logs
|
|
28
28
|
server.tool(
|
|
@@ -31,7 +31,7 @@ const handler = createMcpHandler((server) => {
|
|
|
31
31
|
{
|
|
32
32
|
lines: z.number().optional().describe("Number of recent lines to read (default: 50)"),
|
|
33
33
|
filter: z.string().optional().describe("Filter logs by text content"),
|
|
34
|
-
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
34
|
+
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
35
35
|
},
|
|
36
36
|
async ({ lines = 50, filter, logPath = "./ai-dev-tools/consolidated.log" }) => {
|
|
37
37
|
try {
|
|
@@ -43,44 +43,40 @@ const handler = createMcpHandler((server) => {
|
|
|
43
43
|
text: `No log file found at ${logPath}. Make sure the dev environment is running.`
|
|
44
44
|
}
|
|
45
45
|
]
|
|
46
|
-
}
|
|
46
|
+
}
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
const logContent = readFileSync(logPath, "utf-8")
|
|
50
|
-
let logLines = logContent.split("\n").filter(line => line.trim())
|
|
51
|
-
|
|
48
|
+
|
|
49
|
+
const logContent = readFileSync(logPath, "utf-8")
|
|
50
|
+
let logLines = logContent.split("\n").filter((line) => line.trim())
|
|
51
|
+
|
|
52
52
|
// Apply filter if provided
|
|
53
53
|
if (filter) {
|
|
54
|
-
logLines = logLines.filter(line =>
|
|
55
|
-
line.toLowerCase().includes(filter.toLowerCase())
|
|
56
|
-
);
|
|
54
|
+
logLines = logLines.filter((line) => line.toLowerCase().includes(filter.toLowerCase()))
|
|
57
55
|
}
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
// Get recent lines
|
|
60
|
-
const recentLines = logLines.slice(-lines)
|
|
61
|
-
|
|
58
|
+
const recentLines = logLines.slice(-lines)
|
|
59
|
+
|
|
62
60
|
return {
|
|
63
61
|
content: [
|
|
64
62
|
{
|
|
65
63
|
type: "text",
|
|
66
|
-
text: recentLines.length > 0
|
|
67
|
-
? recentLines.join("\n")
|
|
68
|
-
: "No matching log entries found."
|
|
64
|
+
text: recentLines.length > 0 ? recentLines.join("\n") : "No matching log entries found."
|
|
69
65
|
}
|
|
70
66
|
]
|
|
71
|
-
}
|
|
67
|
+
}
|
|
72
68
|
} catch (error) {
|
|
73
69
|
return {
|
|
74
70
|
content: [
|
|
75
71
|
{
|
|
76
|
-
type: "text",
|
|
72
|
+
type: "text",
|
|
77
73
|
text: `Error reading logs: ${error instanceof Error ? error.message : String(error)}`
|
|
78
74
|
}
|
|
79
75
|
]
|
|
80
|
-
}
|
|
76
|
+
}
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
|
-
)
|
|
79
|
+
)
|
|
84
80
|
|
|
85
81
|
// Tool to search logs
|
|
86
82
|
server.tool(
|
|
@@ -89,7 +85,7 @@ const handler = createMcpHandler((server) => {
|
|
|
89
85
|
{
|
|
90
86
|
pattern: z.string().describe("Regex pattern to search for"),
|
|
91
87
|
context: z.number().optional().describe("Number of lines of context around matches (default: 2)"),
|
|
92
|
-
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
88
|
+
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
93
89
|
},
|
|
94
90
|
async ({ pattern, context = 2, logPath = "./ai-dev-tools/consolidated.log" }) => {
|
|
95
91
|
try {
|
|
@@ -101,35 +97,33 @@ const handler = createMcpHandler((server) => {
|
|
|
101
97
|
text: `No log file found at ${logPath}.`
|
|
102
98
|
}
|
|
103
99
|
]
|
|
104
|
-
}
|
|
100
|
+
}
|
|
105
101
|
}
|
|
106
|
-
|
|
107
|
-
const logContent = readFileSync(logPath, "utf-8")
|
|
108
|
-
const logLines = logContent.split("\n")
|
|
109
|
-
|
|
110
|
-
const regex = new RegExp(pattern, "gi")
|
|
111
|
-
const matches: string[] = []
|
|
112
|
-
|
|
102
|
+
|
|
103
|
+
const logContent = readFileSync(logPath, "utf-8")
|
|
104
|
+
const logLines = logContent.split("\n")
|
|
105
|
+
|
|
106
|
+
const regex = new RegExp(pattern, "gi")
|
|
107
|
+
const matches: string[] = []
|
|
108
|
+
|
|
113
109
|
logLines.forEach((line, index) => {
|
|
114
110
|
if (regex.test(line)) {
|
|
115
|
-
const start = Math.max(0, index - context)
|
|
116
|
-
const end = Math.min(logLines.length, index + context + 1)
|
|
117
|
-
const contextLines = logLines.slice(start, end)
|
|
118
|
-
|
|
119
|
-
matches.push(`Match at line ${index + 1}:\n${contextLines.join("\n")}\n---`)
|
|
111
|
+
const start = Math.max(0, index - context)
|
|
112
|
+
const end = Math.min(logLines.length, index + context + 1)
|
|
113
|
+
const contextLines = logLines.slice(start, end)
|
|
114
|
+
|
|
115
|
+
matches.push(`Match at line ${index + 1}:\n${contextLines.join("\n")}\n---`)
|
|
120
116
|
}
|
|
121
|
-
})
|
|
122
|
-
|
|
117
|
+
})
|
|
118
|
+
|
|
123
119
|
return {
|
|
124
120
|
content: [
|
|
125
121
|
{
|
|
126
122
|
type: "text",
|
|
127
|
-
text: matches.length > 0
|
|
128
|
-
? matches.join("\n\n")
|
|
129
|
-
: "No matches found for the given pattern."
|
|
123
|
+
text: matches.length > 0 ? matches.join("\n\n") : "No matches found for the given pattern."
|
|
130
124
|
}
|
|
131
125
|
]
|
|
132
|
-
}
|
|
126
|
+
}
|
|
133
127
|
} catch (error) {
|
|
134
128
|
return {
|
|
135
129
|
content: [
|
|
@@ -138,10 +132,10 @@ const handler = createMcpHandler((server) => {
|
|
|
138
132
|
text: `Error searching logs: ${error instanceof Error ? error.message : String(error)}`
|
|
139
133
|
}
|
|
140
134
|
]
|
|
141
|
-
}
|
|
135
|
+
}
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
|
-
)
|
|
138
|
+
)
|
|
145
139
|
|
|
146
140
|
// Tool to get browser errors
|
|
147
141
|
server.tool(
|
|
@@ -149,7 +143,7 @@ const handler = createMcpHandler((server) => {
|
|
|
149
143
|
"Get recent browser errors and page errors from logs",
|
|
150
144
|
{
|
|
151
145
|
hours: z.number().optional().describe("Hours to look back (default: 1)"),
|
|
152
|
-
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
146
|
+
logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
|
|
153
147
|
},
|
|
154
148
|
async ({ hours = 1, logPath = "./ai-dev-tools/consolidated.log" }) => {
|
|
155
149
|
try {
|
|
@@ -161,36 +155,37 @@ const handler = createMcpHandler((server) => {
|
|
|
161
155
|
text: `No log file found at ${logPath}.`
|
|
162
156
|
}
|
|
163
157
|
]
|
|
164
|
-
}
|
|
158
|
+
}
|
|
165
159
|
}
|
|
166
|
-
|
|
167
|
-
const logContent = readFileSync(logPath, "utf-8")
|
|
168
|
-
const logLines = logContent.split("\n")
|
|
169
|
-
|
|
170
|
-
const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000)
|
|
171
|
-
const errorLines = logLines.filter(line => {
|
|
172
|
-
if (!line.includes("[BROWSER]")) return false
|
|
173
|
-
if (!(line.includes("ERROR") || line.includes("CONSOLE ERROR") || line.includes("PAGE ERROR"))) return false
|
|
174
|
-
|
|
160
|
+
|
|
161
|
+
const logContent = readFileSync(logPath, "utf-8")
|
|
162
|
+
const logLines = logContent.split("\n")
|
|
163
|
+
|
|
164
|
+
const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000)
|
|
165
|
+
const errorLines = logLines.filter((line) => {
|
|
166
|
+
if (!line.includes("[BROWSER]")) return false
|
|
167
|
+
if (!(line.includes("ERROR") || line.includes("CONSOLE ERROR") || line.includes("PAGE ERROR"))) return false
|
|
168
|
+
|
|
175
169
|
// Extract timestamp
|
|
176
|
-
const timestampMatch = line.match(/\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\]/)
|
|
170
|
+
const timestampMatch = line.match(/\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\]/)
|
|
177
171
|
if (timestampMatch) {
|
|
178
|
-
const logTime = new Date(timestampMatch[1])
|
|
179
|
-
return logTime > cutoffTime
|
|
172
|
+
const logTime = new Date(timestampMatch[1])
|
|
173
|
+
return logTime > cutoffTime
|
|
180
174
|
}
|
|
181
|
-
return true
|
|
182
|
-
})
|
|
183
|
-
|
|
175
|
+
return true // Include if we can't parse timestamp
|
|
176
|
+
})
|
|
177
|
+
|
|
184
178
|
return {
|
|
185
179
|
content: [
|
|
186
180
|
{
|
|
187
181
|
type: "text",
|
|
188
|
-
text:
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
text:
|
|
183
|
+
errorLines.length > 0
|
|
184
|
+
? errorLines.join("\n")
|
|
185
|
+
: "No browser errors found in the specified time period."
|
|
191
186
|
}
|
|
192
187
|
]
|
|
193
|
-
}
|
|
188
|
+
}
|
|
194
189
|
} catch (error) {
|
|
195
190
|
return {
|
|
196
191
|
content: [
|
|
@@ -199,10 +194,10 @@ const handler = createMcpHandler((server) => {
|
|
|
199
194
|
text: `Error getting browser errors: ${error instanceof Error ? error.message : String(error)}`
|
|
200
195
|
}
|
|
201
196
|
]
|
|
202
|
-
}
|
|
197
|
+
}
|
|
203
198
|
}
|
|
204
199
|
}
|
|
205
|
-
)
|
|
200
|
+
)
|
|
206
201
|
|
|
207
202
|
// Tool to execute browser actions via CDP
|
|
208
203
|
server.tool(
|
|
@@ -210,83 +205,85 @@ const handler = createMcpHandler((server) => {
|
|
|
210
205
|
"Execute safe browser actions via Chrome DevTools Protocol",
|
|
211
206
|
{
|
|
212
207
|
action: z.enum(["click", "navigate", "screenshot", "evaluate", "scroll", "type"]).describe("Action to perform"),
|
|
213
|
-
params: z
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
208
|
+
params: z
|
|
209
|
+
.object({
|
|
210
|
+
x: z.number().optional().describe("X coordinate for click/scroll"),
|
|
211
|
+
y: z.number().optional().describe("Y coordinate for click/scroll"),
|
|
212
|
+
url: z.string().optional().describe("URL for navigation"),
|
|
213
|
+
selector: z.string().optional().describe("CSS selector for element targeting"),
|
|
214
|
+
text: z.string().optional().describe("Text to type"),
|
|
215
|
+
expression: z.string().optional().describe("JavaScript expression to evaluate (safe expressions only)"),
|
|
216
|
+
deltaX: z.number().optional().describe("Horizontal scroll amount"),
|
|
217
|
+
deltaY: z.number().optional().describe("Vertical scroll amount")
|
|
218
|
+
})
|
|
219
|
+
.describe("Parameters for the action")
|
|
223
220
|
},
|
|
224
221
|
async ({ action, params }) => {
|
|
225
222
|
try {
|
|
226
223
|
// Connect to CDP on port 9222
|
|
227
|
-
const targetsResponse = await fetch(
|
|
228
|
-
const targets = await targetsResponse.json()
|
|
229
|
-
|
|
230
|
-
const pageTarget = targets.find((target: any) => target.type ===
|
|
224
|
+
const targetsResponse = await fetch("http://localhost:9222/json")
|
|
225
|
+
const targets = await targetsResponse.json()
|
|
226
|
+
|
|
227
|
+
const pageTarget = targets.find((target: any) => target.type === "page")
|
|
231
228
|
if (!pageTarget) {
|
|
232
|
-
throw new Error(
|
|
229
|
+
throw new Error("No browser tab found. Make sure dev3000 is running with CDP monitoring.")
|
|
233
230
|
}
|
|
234
231
|
|
|
235
|
-
const wsUrl = pageTarget.webSocketDebuggerUrl
|
|
236
|
-
|
|
232
|
+
const wsUrl = pageTarget.webSocketDebuggerUrl
|
|
233
|
+
|
|
237
234
|
const result = await new Promise((resolve, reject) => {
|
|
238
235
|
// WebSocket imported at top of file
|
|
239
|
-
const ws = new WebSocket(wsUrl)
|
|
240
|
-
let messageId = 1
|
|
241
|
-
|
|
242
|
-
ws.on(
|
|
236
|
+
const ws = new WebSocket(wsUrl)
|
|
237
|
+
let messageId = 1
|
|
238
|
+
|
|
239
|
+
ws.on("open", async () => {
|
|
243
240
|
try {
|
|
244
|
-
let cdpResult
|
|
245
|
-
|
|
241
|
+
let cdpResult
|
|
242
|
+
|
|
246
243
|
switch (action) {
|
|
247
|
-
case
|
|
244
|
+
case "click":
|
|
248
245
|
if (!params.x || !params.y) {
|
|
249
|
-
throw new Error(
|
|
246
|
+
throw new Error("Click action requires x and y coordinates")
|
|
250
247
|
}
|
|
251
248
|
// Send mouse down and up events
|
|
252
|
-
await sendCDPCommand(ws, messageId++,
|
|
253
|
-
type:
|
|
249
|
+
await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
|
|
250
|
+
type: "mousePressed",
|
|
254
251
|
x: params.x,
|
|
255
252
|
y: params.y,
|
|
256
|
-
button:
|
|
253
|
+
button: "left",
|
|
257
254
|
clickCount: 1
|
|
258
|
-
})
|
|
259
|
-
await sendCDPCommand(ws, messageId++,
|
|
260
|
-
type:
|
|
255
|
+
})
|
|
256
|
+
await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
|
|
257
|
+
type: "mouseReleased",
|
|
261
258
|
x: params.x,
|
|
262
259
|
y: params.y,
|
|
263
|
-
button:
|
|
260
|
+
button: "left",
|
|
264
261
|
clickCount: 1
|
|
265
|
-
})
|
|
266
|
-
cdpResult = { action:
|
|
267
|
-
break
|
|
268
|
-
|
|
269
|
-
case
|
|
262
|
+
})
|
|
263
|
+
cdpResult = { action: "click", coordinates: { x: params.x, y: params.y } }
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
case "navigate":
|
|
270
267
|
if (!params.url) {
|
|
271
|
-
throw new Error(
|
|
268
|
+
throw new Error("Navigate action requires url parameter")
|
|
272
269
|
}
|
|
273
270
|
// Basic URL validation
|
|
274
|
-
if (!params.url.startsWith(
|
|
275
|
-
throw new Error(
|
|
271
|
+
if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) {
|
|
272
|
+
throw new Error("Only http:// and https:// URLs are allowed")
|
|
276
273
|
}
|
|
277
|
-
cdpResult = await sendCDPCommand(ws, messageId++,
|
|
278
|
-
break
|
|
279
|
-
|
|
280
|
-
case
|
|
281
|
-
cdpResult = await sendCDPCommand(ws, messageId++,
|
|
282
|
-
format:
|
|
274
|
+
cdpResult = await sendCDPCommand(ws, messageId++, "Page.navigate", { url: params.url })
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
case "screenshot":
|
|
278
|
+
cdpResult = await sendCDPCommand(ws, messageId++, "Page.captureScreenshot", {
|
|
279
|
+
format: "png",
|
|
283
280
|
quality: 80
|
|
284
|
-
})
|
|
285
|
-
break
|
|
286
|
-
|
|
287
|
-
case
|
|
281
|
+
})
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
case "evaluate": {
|
|
288
285
|
if (!params.expression) {
|
|
289
|
-
throw new Error(
|
|
286
|
+
throw new Error("Evaluate action requires expression parameter")
|
|
290
287
|
}
|
|
291
288
|
// Whitelist safe expressions only
|
|
292
289
|
const safeExpressions = [
|
|
@@ -296,87 +293,89 @@ const handler = createMcpHandler((server) => {
|
|
|
296
293
|
/^document\.body\.scrollHeight$/,
|
|
297
294
|
/^window\.scrollY$/,
|
|
298
295
|
/^window\.scrollX$/
|
|
299
|
-
]
|
|
300
|
-
|
|
301
|
-
if (!safeExpressions.some(regex => regex.test(params.expression!))) {
|
|
302
|
-
throw new Error(
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
if (!safeExpressions.some((regex) => regex.test(params.expression!))) {
|
|
299
|
+
throw new Error("Expression not in whitelist. Only safe read-only expressions allowed.")
|
|
303
300
|
}
|
|
304
|
-
|
|
305
|
-
cdpResult = await sendCDPCommand(ws, messageId++,
|
|
301
|
+
|
|
302
|
+
cdpResult = await sendCDPCommand(ws, messageId++, "Runtime.evaluate", {
|
|
306
303
|
expression: params.expression,
|
|
307
304
|
returnByValue: true
|
|
308
|
-
})
|
|
309
|
-
break
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
305
|
+
})
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case "scroll": {
|
|
310
|
+
const scrollX = params.deltaX || 0
|
|
311
|
+
const scrollY = params.deltaY || 0
|
|
312
|
+
cdpResult = await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
|
|
313
|
+
type: "mouseWheel",
|
|
316
314
|
x: params.x || 500,
|
|
317
315
|
y: params.y || 500,
|
|
318
316
|
deltaX: scrollX,
|
|
319
317
|
deltaY: scrollY
|
|
320
|
-
})
|
|
321
|
-
break
|
|
322
|
-
|
|
323
|
-
|
|
318
|
+
})
|
|
319
|
+
break
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
case "type":
|
|
324
323
|
if (!params.text) {
|
|
325
|
-
throw new Error(
|
|
324
|
+
throw new Error("Type action requires text parameter")
|
|
326
325
|
}
|
|
327
326
|
// Type each character
|
|
328
327
|
for (const char of params.text) {
|
|
329
|
-
await sendCDPCommand(ws, messageId++,
|
|
330
|
-
type:
|
|
328
|
+
await sendCDPCommand(ws, messageId++, "Input.dispatchKeyEvent", {
|
|
329
|
+
type: "char",
|
|
331
330
|
text: char
|
|
332
|
-
})
|
|
331
|
+
})
|
|
333
332
|
}
|
|
334
|
-
cdpResult = { action:
|
|
335
|
-
break
|
|
336
|
-
|
|
333
|
+
cdpResult = { action: "type", text: params.text }
|
|
334
|
+
break
|
|
335
|
+
|
|
337
336
|
default:
|
|
338
|
-
throw new Error(`Unsupported action: ${action}`)
|
|
337
|
+
throw new Error(`Unsupported action: ${action}`)
|
|
339
338
|
}
|
|
340
|
-
|
|
341
|
-
ws.close()
|
|
342
|
-
resolve(cdpResult)
|
|
339
|
+
|
|
340
|
+
ws.close()
|
|
341
|
+
resolve(cdpResult)
|
|
343
342
|
} catch (error) {
|
|
344
|
-
ws.close()
|
|
345
|
-
reject(error)
|
|
343
|
+
ws.close()
|
|
344
|
+
reject(error)
|
|
346
345
|
}
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
ws.on(
|
|
350
|
-
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
ws.on("error", reject)
|
|
349
|
+
|
|
351
350
|
// Helper function to send CDP commands
|
|
352
351
|
async function sendCDPCommand(ws: any, id: number, method: string, params: any): Promise<any> {
|
|
353
352
|
return new Promise((cmdResolve, cmdReject) => {
|
|
354
|
-
const command = { id, method, params }
|
|
355
|
-
|
|
353
|
+
const command = { id, method, params }
|
|
354
|
+
|
|
356
355
|
const messageHandler = (data: any) => {
|
|
357
|
-
const message = JSON.parse(data.toString())
|
|
356
|
+
const message = JSON.parse(data.toString())
|
|
358
357
|
if (message.id === id) {
|
|
359
|
-
ws.removeListener(
|
|
358
|
+
ws.removeListener("message", messageHandler)
|
|
360
359
|
if (message.error) {
|
|
361
|
-
cmdReject(new Error(message.error.message))
|
|
360
|
+
cmdReject(new Error(message.error.message))
|
|
362
361
|
} else {
|
|
363
|
-
cmdResolve(message.result)
|
|
362
|
+
cmdResolve(message.result)
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
ws.on(
|
|
369
|
-
ws.send(JSON.stringify(command))
|
|
370
|
-
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
ws.on("message", messageHandler)
|
|
368
|
+
ws.send(JSON.stringify(command))
|
|
369
|
+
|
|
371
370
|
// Command timeout
|
|
372
371
|
setTimeout(() => {
|
|
373
|
-
ws.removeListener(
|
|
374
|
-
cmdReject(new Error(`CDP command timeout: ${method}`))
|
|
375
|
-
}, 5000)
|
|
376
|
-
})
|
|
372
|
+
ws.removeListener("message", messageHandler)
|
|
373
|
+
cmdReject(new Error(`CDP command timeout: ${method}`))
|
|
374
|
+
}, 5000)
|
|
375
|
+
})
|
|
377
376
|
}
|
|
378
|
-
})
|
|
379
|
-
|
|
377
|
+
})
|
|
378
|
+
|
|
380
379
|
return {
|
|
381
380
|
content: [
|
|
382
381
|
{
|
|
@@ -384,8 +383,7 @@ const handler = createMcpHandler((server) => {
|
|
|
384
383
|
text: `Browser action '${action}' executed successfully. Result: ${JSON.stringify(result, null, 2)}`
|
|
385
384
|
}
|
|
386
385
|
]
|
|
387
|
-
}
|
|
388
|
-
|
|
386
|
+
}
|
|
389
387
|
} catch (error) {
|
|
390
388
|
return {
|
|
391
389
|
content: [
|
|
@@ -394,16 +392,19 @@ const handler = createMcpHandler((server) => {
|
|
|
394
392
|
text: `Browser action failed: ${error instanceof Error ? error.message : String(error)}`
|
|
395
393
|
}
|
|
396
394
|
]
|
|
397
|
-
}
|
|
395
|
+
}
|
|
398
396
|
}
|
|
399
397
|
}
|
|
400
|
-
)
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
398
|
+
)
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
// Server options
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
basePath: "/api/mcp",
|
|
405
|
+
maxDuration: 60,
|
|
406
|
+
verboseLogs: true
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
export { handler as GET, handler as POST }
|