@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 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 events = store.getNetworkRequests({
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 events = store.getConsoleMessages({
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 truncated = msg.length > 80 ? msg.slice(0, 80) + "..." : msg;
139
- issues.push(`Error spam: "${truncated}" repeated ${info.count} times in ${((info.last - info.first) / 1e3).toFixed(1)}s`);
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 events = store.getStateEvents({
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 events = store.getNetworkRequests({
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
- const context = await br.newContext({
4205
- viewport: { width: viewportWidth, height: viewportHeight },
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
- const context = await br.newContext({
4287
- viewport: { width: 1280, height: 720 }
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
- const context = await br.newContext({
4305
- viewport: { width: 1280, height: 720 }
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
  /**