@remcp/runtime 0.2.169 → 0.2.171

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.171 — 2026-10-21
4
+
5
+ - Require MCP Server SDK 2.3.0 or later; update the published dependency lock to the patched 2.3.x line.
6
+ - Preserve the existing local tool surface (83 tools), OS-specific safeguards, pairing and browser-origin restrictions. No additional permissions are introduced.
7
+
8
+ ## 0.2.170 — 2026-10-20
9
+
10
+ - Reapply per-target CDP viewport emulation when opening a new guarded browser session; bound state to 64 targets and expire after 10 minutes.
11
+ - Fix inaccurate mobile browser QA readback after separate `set_viewport` and DOM/snapshot calls without widening browser-origin permissions.
12
+ - Add viewport persistence, target-isolation and expiry tests.
13
+
3
14
  ## 0.2.169 — 2026-10-19
4
15
 
5
16
  - Emit redacted, bounded lifecycle events when ReMCP-owned Chrome exits and distinguish clean exit, nonzero exit, OS signal and spawn error.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.169",
3
+ "version": "0.2.171",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@jellybrick/dbus-next": "^0.11.3",
28
- "@modelcontextprotocol/server": "^2.0.0"
28
+ "@modelcontextprotocol/server": "^2.3.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@modelcontextprotocol/client": "^2.0.0"
@@ -9,8 +9,9 @@ import { preserveInterruptedBrowserSession } from './browser-session-backup.mjs'
9
9
  import { clamp, commandExists, jsonResult, optionalString, requireEnum, spawnDetached, unavailable } from './common.mjs';
10
10
 
11
11
 
12
- import { assertBrowserRemoteEnabled, assertSafeEvaluationExpression } from './browser-endpoint.mjs';
13
- import { callPageFunction, evaluate, withTarget } from './browser-session.mjs';
12
+ import { assertBrowserRemoteEnabled, assertSafeEvaluationExpression, endpointUrl } from './browser-endpoint.mjs';
13
+ import { callPageFunction, evaluate, withTarget, productionSessionKey } from './browser-session.mjs';
14
+ import { rememberBrowserViewport } from './browser-viewport.mjs';
14
15
  import { browserNavigate, browserSnapshot, browserTabs, findExpression } from './browser-read.mjs';
15
16
  import { executeBrowserWorkflow, validateBrowserWorkflow } from './browser-workflow.mjs';
16
17
 
@@ -226,14 +227,9 @@ export async function browserAction(args) {
226
227
  const width = clamp(args.width, 1280, 200, 8192);
227
228
  const height = clamp(args.height, 720, 200, 8192);
228
229
  const deviceScaleFactor = Math.max(0.1, Math.min(8, Number(args.device_scale_factor) || 1));
229
- await session.send('Emulation.setDeviceMetricsOverride', {
230
- width,
231
- height,
232
- deviceScaleFactor,
233
- mobile: Boolean(args.mobile),
234
- screenWidth: width,
235
- screenHeight: height,
236
- });
230
+ const metrics = { width, height, deviceScaleFactor, mobile:Boolean(args.mobile), screenWidth:width, screenHeight:height };
231
+ await session.send('Emulation.setDeviceMetricsOverride', metrics);
232
+ rememberBrowserViewport(productionSessionKey(endpointUrl(args.endpoint), target), metrics);
237
233
  return jsonResult({ target_id:target.id, action, width, height, device_scale_factor:deviceScaleFactor, mobile:Boolean(args.mobile) });
238
234
  }
239
235
  if (action === 'upload') {
@@ -10,6 +10,7 @@ import { clamp, commandExists, jsonResult, optionalString, requireEnum, spawnDet
10
10
 
11
11
 
12
12
  import { MAX_CDP_MESSAGE_BYTES, PRODUCTION_SESSION_IDLE_MS, assertResolvedBrowserUrl, assertResolvedPageUrl, chooseTarget, endpointUrl, ensureBrowserEndpoint, productionBrowserSessions, requestJson, validatedTargetWebSocketUrl } from './browser-endpoint.mjs';
13
+ import { getBrowserViewport } from './browser-viewport.mjs';
13
14
 
14
15
  export class CdpSession {
15
16
  constructor(url, timeoutMs) {
@@ -384,17 +385,20 @@ export async function withTarget(args = {}, callback) {
384
385
  const cached = findGuardedCachedTarget(args, endpoint);
385
386
  const target = cached?.target || await chooseTarget(args);
386
387
  const production = process.env.NODE_ENV === 'production';
387
- const key = production ? productionSessionKey(endpoint, target) : '';
388
+ const viewportKey = productionSessionKey(endpoint, target);
389
+ const key = production ? viewportKey : '';
388
390
  let record = cached?.record || (production ? productionBrowserSessions.get(key) : null);
389
391
  if (record?.session?.closed || (record?.session?.socket && record.session.socket.readyState !== 1)) {
390
392
  closeProductionSession(key, record);
391
393
  record = null;
392
394
  }
393
395
  let session = record?.session;
396
+ let openedSession = false;
394
397
  let removeGuard = record?.removeGuard || (() => {});
395
398
  if (!session) {
396
399
  try {
397
400
  session = await new CdpSession(target.webSocketDebuggerUrl, clamp(args.timeout_ms, 10_000, 100, 120_000)).open();
401
+ openedSession = true;
398
402
  removeGuard = await installBrowserRequestGuard(session);
399
403
  if (production) record = retainProductionSession(key, session, removeGuard, target.id, endpoint, false, target);
400
404
  } catch (error) {
@@ -408,6 +412,10 @@ export async function withTarget(args = {}, callback) {
408
412
  }
409
413
  let operationFailed = false;
410
414
  try {
415
+ if (openedSession) {
416
+ const viewport = getBrowserViewport(viewportKey);
417
+ if (viewport) await session.send('Emulation.setDeviceMetricsOverride', viewport);
418
+ }
411
419
  const currentUrl = await evaluate(session, 'location.href');
412
420
  await assertResolvedPageUrl(currentUrl);
413
421
  // A reused target's discovery metadata can be old after navigation.
@@ -0,0 +1,22 @@
1
+ // CDP Emulation settings belong to a protocol session. Cache only bounded,
2
+ // non-sensitive viewport parameters so a newly opened session can reapply them.
3
+ const MAX_TARGETS = 64;
4
+ const IDLE_MS = 10 * 60_000;
5
+ const overrides = new Map();
6
+
7
+ export function rememberBrowserViewport(key, metrics, at = Date.now()) {
8
+ if (!key) return;
9
+ overrides.delete(key);
10
+ overrides.set(key, { metrics: { ...metrics }, expiresAt: at + IDLE_MS });
11
+ for (const [target, entry] of overrides) {
12
+ if (entry.expiresAt <= at) overrides.delete(target);
13
+ }
14
+ while (overrides.size > MAX_TARGETS) overrides.delete(overrides.keys().next().value);
15
+ }
16
+
17
+ export function getBrowserViewport(key, at = Date.now()) {
18
+ const entry = overrides.get(key);
19
+ if (!entry) return null;
20
+ if (entry.expiresAt <= at) { overrides.delete(key); return null; }
21
+ return { ...entry.metrics };
22
+ }