@runtimescope/mcp-server 0.7.0 → 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 +124 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
SessionManager,
|
|
19
19
|
HttpServer,
|
|
20
20
|
SqliteStore,
|
|
21
|
+
isSqliteAvailable,
|
|
21
22
|
AuthManager,
|
|
22
23
|
Redactor,
|
|
23
24
|
resolveTlsConfig,
|
|
@@ -35,15 +36,19 @@ function registerNetworkTools(server, store) {
|
|
|
35
36
|
since_seconds: z.number().optional().describe("Only return requests from the last N seconds"),
|
|
36
37
|
url_pattern: z.string().optional().describe("Filter by URL substring match"),
|
|
37
38
|
status: z.number().optional().describe("Filter by HTTP status code"),
|
|
38
|
-
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)")
|
|
39
41
|
},
|
|
40
|
-
async ({ since_seconds, url_pattern, status, method }) => {
|
|
41
|
-
const
|
|
42
|
+
async ({ since_seconds, url_pattern, status, method, limit }) => {
|
|
43
|
+
const allEvents = store.getNetworkRequests({
|
|
42
44
|
sinceSeconds: since_seconds,
|
|
43
45
|
urlPattern: url_pattern,
|
|
44
46
|
status,
|
|
45
47
|
method
|
|
46
48
|
});
|
|
49
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
50
|
+
const truncated = allEvents.length > maxLimit;
|
|
51
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
47
52
|
const timeRange = events.length > 0 ? { from: events[events.length - 1].timestamp, to: events[0].timestamp } : { from: 0, to: 0 };
|
|
48
53
|
const sessions = store.getSessionInfo();
|
|
49
54
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
@@ -71,7 +76,7 @@ function registerNetworkTools(server, store) {
|
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
const response = {
|
|
74
|
-
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.`,
|
|
75
80
|
data: events.map((e) => ({
|
|
76
81
|
url: e.url,
|
|
77
82
|
method: e.method,
|
|
@@ -84,7 +89,7 @@ function registerNetworkTools(server, store) {
|
|
|
84
89
|
timestamp: new Date(e.timestamp).toISOString()
|
|
85
90
|
})),
|
|
86
91
|
issues,
|
|
87
|
-
metadata: { timeRange, eventCount: events.length, sessionId }
|
|
92
|
+
metadata: { timeRange, eventCount: events.length, totalCount: allEvents.length, truncated, sessionId }
|
|
88
93
|
};
|
|
89
94
|
return {
|
|
90
95
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
@@ -102,14 +107,18 @@ function registerConsoleTools(server, store) {
|
|
|
102
107
|
{
|
|
103
108
|
level: z2.enum(["log", "warn", "error", "info", "debug", "trace"]).optional().describe("Filter by console level"),
|
|
104
109
|
since_seconds: z2.number().optional().describe("Only return messages from the last N seconds"),
|
|
105
|
-
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)")
|
|
106
112
|
},
|
|
107
|
-
async ({ level, since_seconds, search }) => {
|
|
108
|
-
const
|
|
113
|
+
async ({ level, since_seconds, search, limit }) => {
|
|
114
|
+
const allEvents = store.getConsoleMessages({
|
|
109
115
|
level,
|
|
110
116
|
sinceSeconds: since_seconds,
|
|
111
117
|
search
|
|
112
118
|
});
|
|
119
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
120
|
+
const truncated = allEvents.length > maxLimit;
|
|
121
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
113
122
|
const timeRange = events.length > 0 ? { from: events[events.length - 1].timestamp, to: events[0].timestamp } : { from: 0, to: 0 };
|
|
114
123
|
const sessions = store.getSessionInfo();
|
|
115
124
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
@@ -134,12 +143,12 @@ function registerConsoleTools(server, store) {
|
|
|
134
143
|
}
|
|
135
144
|
for (const [msg, info] of errorMessages) {
|
|
136
145
|
if (info.count > 5 && info.last - info.first < 1e4) {
|
|
137
|
-
const
|
|
138
|
-
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`);
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
const response = {
|
|
142
|
-
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}` : ""}.`,
|
|
143
152
|
data: events.map((e) => ({
|
|
144
153
|
level: e.level,
|
|
145
154
|
message: e.message,
|
|
@@ -149,7 +158,7 @@ function registerConsoleTools(server, store) {
|
|
|
149
158
|
timestamp: new Date(e.timestamp).toISOString()
|
|
150
159
|
})),
|
|
151
160
|
issues,
|
|
152
|
-
metadata: { timeRange, eventCount: events.length, sessionId }
|
|
161
|
+
metadata: { timeRange, eventCount: events.length, totalCount: allEvents.length, truncated, sessionId }
|
|
153
162
|
};
|
|
154
163
|
return {
|
|
155
164
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
@@ -227,7 +236,7 @@ function registerIssueTools(server, store, apiDiscovery, processMonitor) {
|
|
|
227
236
|
const allIssues = [...detectIssues(events)];
|
|
228
237
|
if (apiDiscovery) {
|
|
229
238
|
try {
|
|
230
|
-
allIssues.push(...apiDiscovery.detectIssues(
|
|
239
|
+
allIssues.push(...apiDiscovery.detectIssues());
|
|
231
240
|
} catch {
|
|
232
241
|
}
|
|
233
242
|
}
|
|
@@ -437,13 +446,17 @@ function registerStateTools(server, store) {
|
|
|
437
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.",
|
|
438
447
|
{
|
|
439
448
|
store_name: z5.string().optional().describe("Filter by store name/ID"),
|
|
440
|
-
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)")
|
|
441
451
|
},
|
|
442
|
-
async ({ store_name, since_seconds }) => {
|
|
443
|
-
const
|
|
452
|
+
async ({ store_name, since_seconds, limit }) => {
|
|
453
|
+
const allEvents = store.getStateEvents({
|
|
444
454
|
storeId: store_name,
|
|
445
455
|
sinceSeconds: since_seconds
|
|
446
456
|
});
|
|
457
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
458
|
+
const truncated = allEvents.length > maxLimit;
|
|
459
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
447
460
|
const sessions = store.getSessionInfo();
|
|
448
461
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
449
462
|
const issues = [];
|
|
@@ -464,7 +477,7 @@ function registerStateTools(server, store) {
|
|
|
464
477
|
}
|
|
465
478
|
}
|
|
466
479
|
const response = {
|
|
467
|
-
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}"` : ""}.`,
|
|
468
481
|
data: events.map((e) => ({
|
|
469
482
|
storeId: e.storeId,
|
|
470
483
|
library: e.library,
|
|
@@ -482,6 +495,8 @@ function registerStateTools(server, store) {
|
|
|
482
495
|
to: events.length > 0 ? events[events.length - 1].timestamp : 0
|
|
483
496
|
},
|
|
484
497
|
eventCount: events.length,
|
|
498
|
+
totalCount: allEvents.length,
|
|
499
|
+
truncated,
|
|
485
500
|
sessionId
|
|
486
501
|
}
|
|
487
502
|
};
|
|
@@ -752,17 +767,21 @@ function registerHarTools(server, store) {
|
|
|
752
767
|
"capture_har",
|
|
753
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.",
|
|
754
769
|
{
|
|
755
|
-
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)")
|
|
756
772
|
},
|
|
757
|
-
async ({ since_seconds }) => {
|
|
758
|
-
const
|
|
773
|
+
async ({ since_seconds, limit }) => {
|
|
774
|
+
const allEvents = store.getNetworkRequests({
|
|
759
775
|
sinceSeconds: since_seconds
|
|
760
776
|
});
|
|
777
|
+
const maxLimit = Math.min(limit ?? 200, 1e3);
|
|
778
|
+
const truncated = allEvents.length > maxLimit;
|
|
779
|
+
const events = truncated ? allEvents.slice(0, maxLimit) : allEvents;
|
|
761
780
|
const sessions = store.getSessionInfo();
|
|
762
781
|
const sessionId = sessions[0]?.sessionId ?? null;
|
|
763
782
|
const har = buildHar(events);
|
|
764
783
|
const response = {
|
|
765
|
-
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.`,
|
|
766
785
|
data: har,
|
|
767
786
|
issues: [],
|
|
768
787
|
metadata: {
|
|
@@ -771,6 +790,8 @@ function registerHarTools(server, store) {
|
|
|
771
790
|
to: events.length > 0 ? events[events.length - 1].timestamp : 0
|
|
772
791
|
},
|
|
773
792
|
eventCount: events.length,
|
|
793
|
+
totalCount: allEvents.length,
|
|
794
|
+
truncated,
|
|
774
795
|
sessionId
|
|
775
796
|
}
|
|
776
797
|
};
|
|
@@ -4127,6 +4148,30 @@ function buildReconEvents(url, title, sessionId, techResults, tokens, layout, a1
|
|
|
4127
4148
|
}
|
|
4128
4149
|
|
|
4129
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
|
+
};
|
|
4130
4175
|
var PlaywrightScanner = class _PlaywrightScanner {
|
|
4131
4176
|
db = null;
|
|
4132
4177
|
jsGlobalPaths = [];
|
|
@@ -4136,6 +4181,7 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4136
4181
|
static IDLE_TIMEOUT = 6e4;
|
|
4137
4182
|
// Close browser after 60s idle
|
|
4138
4183
|
lastScannedUrl = null;
|
|
4184
|
+
contextSemaphore = new Semaphore(2);
|
|
4139
4185
|
/**
|
|
4140
4186
|
* Lazily load the technology database.
|
|
4141
4187
|
*/
|
|
@@ -4200,12 +4246,14 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4200
4246
|
const db = this.ensureDb();
|
|
4201
4247
|
const { browser } = await this.ensureBrowser();
|
|
4202
4248
|
const br = browser;
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
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"
|
|
4206
|
-
});
|
|
4207
|
-
const page = await context.newPage();
|
|
4249
|
+
await this.contextSemaphore.acquire();
|
|
4250
|
+
let context = null;
|
|
4208
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();
|
|
4209
4257
|
let mainResponse = null;
|
|
4210
4258
|
page.on("response", (response) => {
|
|
4211
4259
|
if (!mainResponse && response.request().resourceType() === "document") {
|
|
@@ -4266,7 +4314,9 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4266
4314
|
scanDurationMs
|
|
4267
4315
|
};
|
|
4268
4316
|
} finally {
|
|
4269
|
-
await context.close()
|
|
4317
|
+
if (context) await context.close().catch(() => {
|
|
4318
|
+
});
|
|
4319
|
+
this.contextSemaphore.release();
|
|
4270
4320
|
}
|
|
4271
4321
|
}
|
|
4272
4322
|
/**
|
|
@@ -4282,15 +4332,19 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4282
4332
|
async queryComputedStyles(url, selector, propertyFilter) {
|
|
4283
4333
|
const { browser } = await this.ensureBrowser();
|
|
4284
4334
|
const br = browser;
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
});
|
|
4288
|
-
const page = await context.newPage();
|
|
4335
|
+
await this.contextSemaphore.acquire();
|
|
4336
|
+
let context = null;
|
|
4289
4337
|
try {
|
|
4338
|
+
context = await br.newContext({
|
|
4339
|
+
viewport: { width: 1280, height: 720 }
|
|
4340
|
+
});
|
|
4341
|
+
const page = await context.newPage();
|
|
4290
4342
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
4291
4343
|
return await collectComputedStyles(page, selector, propertyFilter);
|
|
4292
4344
|
} finally {
|
|
4293
|
-
await context.close()
|
|
4345
|
+
if (context) await context.close().catch(() => {
|
|
4346
|
+
});
|
|
4347
|
+
this.contextSemaphore.release();
|
|
4294
4348
|
}
|
|
4295
4349
|
}
|
|
4296
4350
|
/**
|
|
@@ -4300,15 +4354,19 @@ var PlaywrightScanner = class _PlaywrightScanner {
|
|
|
4300
4354
|
async queryElementSnapshot(url, selector, depth = 5) {
|
|
4301
4355
|
const { browser } = await this.ensureBrowser();
|
|
4302
4356
|
const br = browser;
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
});
|
|
4306
|
-
const page = await context.newPage();
|
|
4357
|
+
await this.contextSemaphore.acquire();
|
|
4358
|
+
let context = null;
|
|
4307
4359
|
try {
|
|
4360
|
+
context = await br.newContext({
|
|
4361
|
+
viewport: { width: 1280, height: 720 }
|
|
4362
|
+
});
|
|
4363
|
+
const page = await context.newPage();
|
|
4308
4364
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
4309
4365
|
return await collectElementSnapshot(page, selector, depth);
|
|
4310
4366
|
} finally {
|
|
4311
|
-
await context.close()
|
|
4367
|
+
if (context) await context.close().catch(() => {
|
|
4368
|
+
});
|
|
4369
|
+
this.contextSemaphore.release();
|
|
4312
4370
|
}
|
|
4313
4371
|
}
|
|
4314
4372
|
/**
|
|
@@ -4977,36 +5035,43 @@ async function main() {
|
|
|
4977
5035
|
}
|
|
4978
5036
|
}
|
|
4979
5037
|
}, AUTO_SNAPSHOT_INTERVAL_MS);
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
const
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5038
|
+
if (isSqliteAvailable()) {
|
|
5039
|
+
const RETENTION_DAYS = parseInt(process.env.RUNTIMESCOPE_RETENTION_DAYS ?? "30", 10);
|
|
5040
|
+
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
5041
|
+
for (const projectName of projectManager.listProjects()) {
|
|
5042
|
+
const dbPath = projectManager.getProjectDbPath(projectName);
|
|
5043
|
+
if (existsSync2(dbPath)) {
|
|
5044
|
+
try {
|
|
5045
|
+
const tempStore = new SqliteStore({ dbPath });
|
|
5046
|
+
const deleted = tempStore.deleteOldEvents(cutoffMs);
|
|
5047
|
+
if (deleted > 0) {
|
|
5048
|
+
console.error(`[RuntimeScope] Pruned ${deleted} events older than ${RETENTION_DAYS}d from "${projectName}"`);
|
|
5049
|
+
}
|
|
5050
|
+
tempStore.close();
|
|
5051
|
+
} catch {
|
|
4990
5052
|
}
|
|
4991
|
-
tempStore.close();
|
|
4992
|
-
} catch {
|
|
4993
5053
|
}
|
|
4994
5054
|
}
|
|
4995
5055
|
}
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5056
|
+
let pmStore;
|
|
5057
|
+
let discovery;
|
|
5058
|
+
if (isSqliteAvailable()) {
|
|
5059
|
+
const pmDbPath = join2(projectManager.rootDir, "pm.db");
|
|
5060
|
+
pmStore = new PmStore({ dbPath: pmDbPath });
|
|
5061
|
+
discovery = new ProjectDiscovery(pmStore, projectManager);
|
|
5062
|
+
discovery.discoverAll().then((result) => {
|
|
5063
|
+
console.error(`[RuntimeScope] PM: ${result.projectsDiscovered} projects, ${result.sessionsDiscovered} sessions discovered`);
|
|
5064
|
+
}).catch((err) => {
|
|
5065
|
+
console.error("[RuntimeScope] PM discovery error:", err.message);
|
|
5066
|
+
});
|
|
5067
|
+
}
|
|
5004
5068
|
const httpServer = new HttpServer(store, processMonitor, {
|
|
5005
5069
|
authManager,
|
|
5006
5070
|
allowedOrigins: corsOrigins,
|
|
5007
5071
|
rateLimiter: collector.getRateLimiter(),
|
|
5008
5072
|
pmStore,
|
|
5009
|
-
discovery
|
|
5073
|
+
discovery,
|
|
5074
|
+
getConnectedSessions: () => collector.getConnectedSessions()
|
|
5010
5075
|
});
|
|
5011
5076
|
try {
|
|
5012
5077
|
await httpServer.start({ port: HTTP_PORT2, tls: tlsConfig });
|
|
@@ -5068,7 +5133,7 @@ async function main() {
|
|
|
5068
5133
|
await connectionManager.closeAll();
|
|
5069
5134
|
await httpServer.stop();
|
|
5070
5135
|
collector.stop();
|
|
5071
|
-
pmStore
|
|
5136
|
+
pmStore?.close();
|
|
5072
5137
|
process.exit(0);
|
|
5073
5138
|
};
|
|
5074
5139
|
process.on("SIGINT", () => {
|