@runtimescope/mcp-server 0.7.1 → 0.7.2
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 +93 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36,15 +36,19 @@ function registerNetworkTools(server, store) {
|
|
|
36
36
|
since_seconds: z.number().optional().describe("Only return requests from the last N seconds"),
|
|
37
37
|
url_pattern: z.string().optional().describe("Filter by URL substring match"),
|
|
38
38
|
status: z.number().optional().describe("Filter by HTTP status code"),
|
|
39
|
-
method: z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)")
|
|
39
|
+
method: z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
|
|
40
|
+
limit: z.number().optional().describe("Max results to return (default 200, max 1000)")
|
|
40
41
|
},
|
|
41
|
-
async ({ since_seconds, url_pattern, status, method }) => {
|
|
42
|
-
const
|
|
42
|
+
async ({ since_seconds, url_pattern, status, method, limit }) => {
|
|
43
|
+
const allEvents = store.getNetworkRequests({
|
|
43
44
|
sinceSeconds: since_seconds,
|
|
44
45
|
urlPattern: url_pattern,
|
|
45
46
|
status,
|
|
46
47
|
method
|
|
47
48
|
});
|
|
49
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
50
|
+
const truncated = allEvents.length > maxLimit;
|
|
51
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
48
52
|
const timeRange = events.length > 0 ? { from: events[events.length - 1].timestamp, to: events[0].timestamp } : { from: 0, to: 0 };
|
|
49
53
|
const sessions = store.getSessionInfo();
|
|
50
54
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
@@ -72,7 +76,7 @@ function registerNetworkTools(server, store) {
|
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
const response = {
|
|
75
|
-
summary: `Found ${events.length} network request(s)${since_seconds ? ` in the last ${since_seconds}s` : ""}. Average duration: ${avgDuration}ms.`,
|
|
79
|
+
summary: `Found ${events.length} network request(s)${truncated ? ` (showing ${maxLimit} of ${allEvents.length})` : ""}${since_seconds ? ` in the last ${since_seconds}s` : ""}. Average duration: ${avgDuration}ms.`,
|
|
76
80
|
data: events.map((e) => ({
|
|
77
81
|
url: e.url,
|
|
78
82
|
method: e.method,
|
|
@@ -85,7 +89,7 @@ function registerNetworkTools(server, store) {
|
|
|
85
89
|
timestamp: new Date(e.timestamp).toISOString()
|
|
86
90
|
})),
|
|
87
91
|
issues,
|
|
88
|
-
metadata: { timeRange, eventCount: events.length, sessionId }
|
|
92
|
+
metadata: { timeRange, eventCount: events.length, totalCount: allEvents.length, truncated, sessionId }
|
|
89
93
|
};
|
|
90
94
|
return {
|
|
91
95
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
@@ -103,14 +107,18 @@ function registerConsoleTools(server, store) {
|
|
|
103
107
|
{
|
|
104
108
|
level: z2.enum(["log", "warn", "error", "info", "debug", "trace"]).optional().describe("Filter by console level"),
|
|
105
109
|
since_seconds: z2.number().optional().describe("Only return messages from the last N seconds"),
|
|
106
|
-
search: z2.string().optional().describe("Search message text (case-insensitive substring match)")
|
|
110
|
+
search: z2.string().optional().describe("Search message text (case-insensitive substring match)"),
|
|
111
|
+
limit: z2.number().optional().describe("Max results to return (default 200, max 1000)")
|
|
107
112
|
},
|
|
108
|
-
async ({ level, since_seconds, search }) => {
|
|
109
|
-
const
|
|
113
|
+
async ({ level, since_seconds, search, limit }) => {
|
|
114
|
+
const allEvents = store.getConsoleMessages({
|
|
110
115
|
level,
|
|
111
116
|
sinceSeconds: since_seconds,
|
|
112
117
|
search
|
|
113
118
|
});
|
|
119
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
120
|
+
const truncated = allEvents.length > maxLimit;
|
|
121
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
114
122
|
const timeRange = events.length > 0 ? { from: events[events.length - 1].timestamp, to: events[0].timestamp } : { from: 0, to: 0 };
|
|
115
123
|
const sessions = store.getSessionInfo();
|
|
116
124
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
@@ -135,12 +143,12 @@ function registerConsoleTools(server, store) {
|
|
|
135
143
|
}
|
|
136
144
|
for (const [msg, info] of errorMessages) {
|
|
137
145
|
if (info.count > 5 && info.last - info.first < 1e4) {
|
|
138
|
-
const
|
|
139
|
-
issues.push(`Error spam: "${
|
|
146
|
+
const truncated2 = msg.length > 80 ? msg.slice(0, 80) + "..." : msg;
|
|
147
|
+
issues.push(`Error spam: "${truncated2}" repeated ${info.count} times in ${((info.last - info.first) / 1e3).toFixed(1)}s`);
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
const response = {
|
|
143
|
-
summary: `Found ${events.length} console message(s)${since_seconds ? ` in the last ${since_seconds}s` : ""}${levelSummary ? `. Breakdown: ${levelSummary}` : ""}.`,
|
|
151
|
+
summary: `Found ${events.length} console message(s)${truncated ? ` (showing ${maxLimit} of ${allEvents.length})` : ""}${since_seconds ? ` in the last ${since_seconds}s` : ""}${levelSummary ? `. Breakdown: ${levelSummary}` : ""}.`,
|
|
144
152
|
data: events.map((e) => ({
|
|
145
153
|
level: e.level,
|
|
146
154
|
message: e.message,
|
|
@@ -150,7 +158,7 @@ function registerConsoleTools(server, store) {
|
|
|
150
158
|
timestamp: new Date(e.timestamp).toISOString()
|
|
151
159
|
})),
|
|
152
160
|
issues,
|
|
153
|
-
metadata: { timeRange, eventCount: events.length, sessionId }
|
|
161
|
+
metadata: { timeRange, eventCount: events.length, totalCount: allEvents.length, truncated, sessionId }
|
|
154
162
|
};
|
|
155
163
|
return {
|
|
156
164
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
@@ -438,13 +446,17 @@ function registerStateTools(server, store) {
|
|
|
438
446
|
"Get state store snapshots and diffs from Zustand or Redux stores. Shows state changes over time with action history, mutation frequency, and shallow diffs showing which keys changed.",
|
|
439
447
|
{
|
|
440
448
|
store_name: z5.string().optional().describe("Filter by store name/ID"),
|
|
441
|
-
since_seconds: z5.number().optional().describe("Only return events from the last N seconds")
|
|
449
|
+
since_seconds: z5.number().optional().describe("Only return events from the last N seconds"),
|
|
450
|
+
limit: z5.number().optional().describe("Max results to return (default 200, max 1000)")
|
|
442
451
|
},
|
|
443
|
-
async ({ store_name, since_seconds }) => {
|
|
444
|
-
const
|
|
452
|
+
async ({ store_name, since_seconds, limit }) => {
|
|
453
|
+
const allEvents = store.getStateEvents({
|
|
445
454
|
storeId: store_name,
|
|
446
455
|
sinceSeconds: since_seconds
|
|
447
456
|
});
|
|
457
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
458
|
+
const truncated = allEvents.length > maxLimit;
|
|
459
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
448
460
|
const sessions = store.getSessionInfo();
|
|
449
461
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
450
462
|
const issues = [];
|
|
@@ -465,7 +477,7 @@ function registerStateTools(server, store) {
|
|
|
465
477
|
}
|
|
466
478
|
}
|
|
467
479
|
const response = {
|
|
468
|
-
summary: `Found ${events.length} state event(s)${since_seconds ? ` in the last ${since_seconds}s` : ""}${store_name ? ` for store "${store_name}"` : ""}.`,
|
|
480
|
+
summary: `Found ${events.length} state event(s)${truncated ? ` (showing ${maxLimit} of ${allEvents.length})` : ""}${since_seconds ? ` in the last ${since_seconds}s` : ""}${store_name ? ` for store "${store_name}"` : ""}.`,
|
|
469
481
|
data: events.map((e) => ({
|
|
470
482
|
storeId: e.storeId,
|
|
471
483
|
library: e.library,
|
|
@@ -483,6 +495,8 @@ function registerStateTools(server, store) {
|
|
|
483
495
|
to: events.length > 0 ? events[events.length - 1].timestamp : 0
|
|
484
496
|
},
|
|
485
497
|
eventCount: events.length,
|
|
498
|
+
totalCount: allEvents.length,
|
|
499
|
+
truncated,
|
|
486
500
|
sessionId
|
|
487
501
|
}
|
|
488
502
|
};
|
|
@@ -753,17 +767,21 @@ function registerHarTools(server, store) {
|
|
|
753
767
|
"capture_har",
|
|
754
768
|
"Export captured network requests as a HAR (HTTP Archive) 1.2 JSON file. This is the standard format used by browser DevTools, Charles Proxy, and other tools. Includes request/response headers, body content (if captureBody was enabled in the SDK), and timing data.",
|
|
755
769
|
{
|
|
756
|
-
since_seconds: z9.number().optional().describe("Only include requests from the last N seconds")
|
|
770
|
+
since_seconds: z9.number().optional().describe("Only include requests from the last N seconds"),
|
|
771
|
+
limit: z9.number().optional().describe("Max entries to include (default 200, max 1000)")
|
|
757
772
|
},
|
|
758
|
-
async ({ since_seconds }) => {
|
|
759
|
-
const
|
|
773
|
+
async ({ since_seconds, limit }) => {
|
|
774
|
+
const allEvents = store.getNetworkRequests({
|
|
760
775
|
sinceSeconds: since_seconds
|
|
761
776
|
});
|
|
777
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
778
|
+
const truncated = allEvents.length > maxLimit;
|
|
779
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
762
780
|
const sessions = store.getSessionInfo();
|
|
763
781
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
764
782
|
const har = buildHar(events);
|
|
765
783
|
const response = {
|
|
766
|
-
summary: `HAR export: ${events.length} request(s)${since_seconds ? ` from the last ${since_seconds}s` : ""}. Import into Chrome DevTools or any HAR viewer.`,
|
|
784
|
+
summary: `HAR export: ${events.length} request(s)${truncated ? ` (showing ${maxLimit} of ${allEvents.length})` : ""}${since_seconds ? ` from the last ${since_seconds}s` : ""}. Import into Chrome DevTools or any HAR viewer.`,
|
|
767
785
|
data: har,
|
|
768
786
|
issues: [],
|
|
769
787
|
metadata: {
|
|
@@ -772,6 +790,8 @@ function registerHarTools(server, store) {
|
|
|
772
790
|
to: events.length > 0 ? events[events.length - 1].timestamp : 0
|
|
773
791
|
},
|
|
774
792
|
eventCount: events.length,
|
|
793
|
+
totalCount: allEvents.length,
|
|
794
|
+
truncated,
|
|
775
795
|
sessionId
|
|
776
796
|
}
|
|
777
797
|
};
|
|
@@ -4128,6 +4148,30 @@ function buildReconEvents(url, title, sessionId, techResults, tokens, layout, a1
|
|
|
4128
4148
|
}
|
|
4129
4149
|
|
|
4130
4150
|
// src/scanner/index.ts
|
|
4151
|
+
var Semaphore = class {
|
|
4152
|
+
constructor(max) {
|
|
4153
|
+
this.max = max;
|
|
4154
|
+
}
|
|
4155
|
+
queue = [];
|
|
4156
|
+
active = 0;
|
|
4157
|
+
async acquire() {
|
|
4158
|
+
if (this.active < this.max) {
|
|
4159
|
+
this.active++;
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
4162
|
+
return new Promise((resolve2) => {
|
|
4163
|
+
this.queue.push(resolve2);
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4166
|
+
release() {
|
|
4167
|
+
this.active--;
|
|
4168
|
+
const next = this.queue.shift();
|
|
4169
|
+
if (next) {
|
|
4170
|
+
this.active++;
|
|
4171
|
+
next();
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
};
|
|
4131
4175
|
var PlaywrightScanner = class _PlaywrightScanner {
|
|
4132
4176
|
db = null;
|
|
4133
4177
|
jsGlobalPaths = [];
|
|
@@ -4137,6 +4181,7 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4137
4181
|
static IDLE_TIMEOUT = 6e4;
|
|
4138
4182
|
// Close browser after 60s idle
|
|
4139
4183
|
lastScannedUrl = null;
|
|
4184
|
+
contextSemaphore = new Semaphore(2);
|
|
4140
4185
|
/**
|
|
4141
4186
|
* Lazily load the technology database.
|
|
4142
4187
|
*/
|
|
@@ -4201,12 +4246,14 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4201
4246
|
const db = this.ensureDb();
|
|
4202
4247
|
const { browser } = await this.ensureBrowser();
|
|
4203
4248
|
const br = browser;
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
4207
|
-
});
|
|
4208
|
-
const page = await context.newPage();
|
|
4249
|
+
await this.contextSemaphore.acquire();
|
|
4250
|
+
let context = null;
|
|
4209
4251
|
try {
|
|
4252
|
+
context = await br.newContext({
|
|
4253
|
+
viewport: { width: viewportWidth, height: viewportHeight },
|
|
4254
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
4255
|
+
});
|
|
4256
|
+
const page = await context.newPage();
|
|
4210
4257
|
let mainResponse = null;
|
|
4211
4258
|
page.on("response", (response) => {
|
|
4212
4259
|
if (!mainResponse && response.request().resourceType() === "document") {
|
|
@@ -4267,7 +4314,9 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4267
4314
|
scanDurationMs
|
|
4268
4315
|
};
|
|
4269
4316
|
} finally {
|
|
4270
|
-
await context.close()
|
|
4317
|
+
if (context) await context.close().catch(() => {
|
|
4318
|
+
});
|
|
4319
|
+
this.contextSemaphore.release();
|
|
4271
4320
|
}
|
|
4272
4321
|
}
|
|
4273
4322
|
/**
|
|
@@ -4283,15 +4332,19 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4283
4332
|
async queryComputedStyles(url, selector, propertyFilter) {
|
|
4284
4333
|
const { browser } = await this.ensureBrowser();
|
|
4285
4334
|
const br = browser;
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
});
|
|
4289
|
-
const page = await context.newPage();
|
|
4335
|
+
await this.contextSemaphore.acquire();
|
|
4336
|
+
let context = null;
|
|
4290
4337
|
try {
|
|
4338
|
+
context = await br.newContext({
|
|
4339
|
+
viewport: { width: 1280, height: 720 }
|
|
4340
|
+
});
|
|
4341
|
+
const page = await context.newPage();
|
|
4291
4342
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
4292
4343
|
return await collectComputedStyles(page, selector, propertyFilter);
|
|
4293
4344
|
} finally {
|
|
4294
|
-
await context.close()
|
|
4345
|
+
if (context) await context.close().catch(() => {
|
|
4346
|
+
});
|
|
4347
|
+
this.contextSemaphore.release();
|
|
4295
4348
|
}
|
|
4296
4349
|
}
|
|
4297
4350
|
/**
|
|
@@ -4301,15 +4354,19 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4301
4354
|
async queryElementSnapshot(url, selector, depth = 5) {
|
|
4302
4355
|
const { browser } = await this.ensureBrowser();
|
|
4303
4356
|
const br = browser;
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
});
|
|
4307
|
-
const page = await context.newPage();
|
|
4357
|
+
await this.contextSemaphore.acquire();
|
|
4358
|
+
let context = null;
|
|
4308
4359
|
try {
|
|
4360
|
+
context = await br.newContext({
|
|
4361
|
+
viewport: { width: 1280, height: 720 }
|
|
4362
|
+
});
|
|
4363
|
+
const page = await context.newPage();
|
|
4309
4364
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
4310
4365
|
return await collectElementSnapshot(page, selector, depth);
|
|
4311
4366
|
} finally {
|
|
4312
|
-
await context.close()
|
|
4367
|
+
if (context) await context.close().catch(() => {
|
|
4368
|
+
});
|
|
4369
|
+
this.contextSemaphore.release();
|
|
4313
4370
|
}
|
|
4314
4371
|
}
|
|
4315
4372
|
/**
|