machine-bridge-mcp 0.12.2 → 0.14.0

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.
@@ -7,7 +7,7 @@ import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } fro
7
7
  import { RelayConnection } from "./relay-connection.mjs";
8
8
  import { applyUpdateHunks, parsePatchEnvelope } from "./patch.mjs";
9
9
  import { executionEnv, workspaceShellCommand } from "./shell.mjs";
10
- import { MAX_COMMAND_BYTES, ProcessSessionManager, terminateProcessTree, validateArgv } from "./process-sessions.mjs";
10
+ import { MAX_COMMAND_BYTES, ProcessSessionManager, terminateProcessTree, terminateProcessTreeWithEscalation, validateArgv } from "./process-sessions.mjs";
11
11
  export { MAX_COMMAND_BYTES } from "./process-sessions.mjs";
12
12
  import { allToolNames, assertCanonicalFullPolicy, isCanonicalFullPolicy, MCP_PROTOCOL_VERSION, MCP_SUPPORTED_PROTOCOL_VERSIONS, normalizePolicy, POLICY_PROFILES, SERVER_NAME, toolNamesForPolicy } from "./tools.mjs";
13
13
  import { classifyOperationalError } from "./log.mjs";
@@ -17,6 +17,7 @@ import { expandHome } from "./state.mjs";
17
17
  import { AgentContextManager } from "./agent-context.mjs";
18
18
  import { AppAutomationManager } from "./app-automation.mjs";
19
19
  import { BrowserBridgeManager } from "./browser-bridge.mjs";
20
+ import { CapabilityObserver } from "./capability-observer.mjs";
20
21
  import { readBoundedRegularFileSync } from "./secure-file.mjs";
21
22
 
22
23
  export const MAX_WRITE_BYTES = 5 * 1024 * 1024;
@@ -45,6 +46,8 @@ const RUNTIME_TOOL_HANDLERS = Object.freeze({
45
46
  browser_status: (runtime, _args, context) => runtime.browserBridgeManager.status(context),
46
47
  pair_browser_extension: (runtime, args, context) => runtime.browserBridgeManager.pair(args, context),
47
48
  browser_list_tabs: (runtime, args, context) => runtime.browserBridgeManager.listTabs(args, context),
49
+ browser_manage_tabs: (runtime, args, context) => runtime.browserBridgeManager.manageTabs(args, context),
50
+ browser_wait: (runtime, args, context) => runtime.browserBridgeManager.wait(args, context),
48
51
  browser_get_source: (runtime, args, context) => runtime.browserBridgeManager.getSource(args, context),
49
52
  browser_inspect_page: (runtime, args, context) => runtime.browserBridgeManager.inspectPage(args, context),
50
53
  browser_action: (runtime, args, context) => runtime.browserBridgeManager.act(args, context),
@@ -85,7 +88,7 @@ export function runtimeToolHandlerNames() {
85
88
  }
86
89
 
87
90
  export class LocalRuntime {
88
- constructor({ workerUrl = "", secret = "", expectedRelayVersion = "", workspace, policy, logger = console, onSuperseded = null, onFatal = null, jobRoot = "", resources = {}, resourceStatePath = "", browserStateRoot = "", agentHome = process.env.HOME || process.env.USERPROFILE || "", codexHome = process.env.CODEX_HOME || "", recoverJobs = true }) {
91
+ constructor({ workerUrl = "", secret = "", expectedRelayVersion = "", workspace, policy, logger = console, onSuperseded = null, onFatal = null, jobRoot = "", resources = {}, resourceStatePath = "", browserStateRoot = "", agentHome = process.env.HOME || process.env.USERPROFILE || "", codexHome = process.env.CODEX_HOME || "", recoverJobs = true, applicationAutomation = {} }) {
89
92
  const remoteWorkerUrl = workerUrl ? String(workerUrl) : "";
90
93
  const remoteSecret = secret || "";
91
94
  this.workspaceInput = resolve(workspace || process.cwd());
@@ -100,6 +103,7 @@ export class LocalRuntime {
100
103
  this.callProcesses = new Map();
101
104
  this.cancelledCalls = new Set();
102
105
  this.mutationQueue = Promise.resolve();
106
+ this.capabilityObserver = new CapabilityObserver();
103
107
  this.runtimeDir = createRuntimeDir();
104
108
  if (typeof jobRoot !== "string" || !jobRoot.trim()) throw new Error("persistent managed-job root is required");
105
109
  this.managedJobManager = new ManagedJobManager({
@@ -139,6 +143,7 @@ export class LocalRuntime {
139
143
  const readResourceText = (name) => this.readLocalResourceText(name);
140
144
  const readResourceBinary = (name) => this.readLocalResourceBinary(name);
141
145
  this.appAutomationManager = new AppAutomationManager({
146
+ ...applicationAutomation,
142
147
  policy: this.policy,
143
148
  displayPath: (value) => this.displayPath(value),
144
149
  runProcess,
@@ -198,9 +203,11 @@ export class LocalRuntime {
198
203
  per_tool_events: "debug-only",
199
204
  default_logs_include_tool_failures: false,
200
205
  tool_arguments_or_results_logged: false,
206
+ capability_routing: this.capabilityObserver.snapshot(),
201
207
  },
202
208
  runtime: {
203
209
  environment: this.policy.minimalEnv ? "isolated-minimal" : "full-parent",
210
+ relay: this.relay?.status?.() || null,
204
211
  runtime_dir: this.policy.exposeAbsolutePaths ? this.runtimeDir : "<private-runtime-dir>",
205
212
  process_sessions: this.processSessionManager.status(),
206
213
  managed_jobs: this.managedJobManager.status(),
@@ -323,14 +330,8 @@ export class LocalRuntime {
323
330
  this.cancelledCalls.add(callId);
324
331
  this.processSessionManager.notifyCancellation();
325
332
  this.browserBridgeManager?.cancelCall(callId);
326
- for (const child of this.callProcesses.get(callId) || []) terminateProcessTree(child, "SIGTERM");
327
333
  const children = [...(this.callProcesses.get(callId) || [])];
328
- if (children.length) {
329
- const timer = setTimeout(() => {
330
- for (const child of children) if (this.activeProcesses.has(child)) terminateProcessTree(child, "SIGKILL");
331
- }, 2000);
332
- timer.unref?.();
333
- }
334
+ for (const child of children) terminateProcessTreeWithEscalation(child);
334
335
  this.logger.debug?.("tool call cancellation requested", { call_id: shortCallId(callId), reason });
335
336
  }
336
337
 
@@ -351,6 +352,7 @@ export class LocalRuntime {
351
352
  gitRoot: git.code === 0 ? this.displayPath(git.stdout.trim()) : "",
352
353
  policy: this.policy,
353
354
  tools: ["server_info", ...this.tools()],
355
+ capabilityRouting: this.capabilityObserver.snapshot(),
354
356
  topLevel: top.entries || [],
355
357
  };
356
358
  }
@@ -795,13 +797,14 @@ export class LocalRuntime {
795
797
  status_tool: "browser_status",
796
798
  } : null,
797
799
  };
800
+ this.capabilityObserver.recordBootstrap(bootstrap);
798
801
  return bootstrap;
799
802
  }
800
803
 
801
804
  async resolveTaskCapabilities(args = {}, context = {}) {
802
805
  const result = await this.agentContextManager.resolveTaskCapabilities(args, context);
803
806
  const task = String(args.task || "");
804
- if (/app|application|gui|window|应用|软件|窗口|界面/i.test(task) && this.policy.profile === "full") {
807
+ if (this.policy.profile === "full") {
805
808
  const applications = await this.appAutomationManager.listApplications({ query: "", max_results: 500 }, context).catch(() => ({ applications: [] }));
806
809
  const lower = task.toLowerCase();
807
810
  result.application_matches = applications.applications
@@ -813,7 +816,12 @@ export class LocalRuntime {
813
816
  } else {
814
817
  result.application_matches = [];
815
818
  }
819
+ if (result.application_matches.length) {
820
+ result.recommended_tools = [...new Set([...result.recommended_tools, "list_local_applications", "open_local_application", "inspect_local_application", "operate_local_application"])];
821
+ }
816
822
  result.browser_backend = this.policy.profile === "full" ? { tool: "browser_status", existing_profile: true, extension_bridge: true } : null;
823
+ result.routing_observability = "Call server_info or project_overview to verify that bootstrap and task capability resolution reached the local runtime.";
824
+ this.capabilityObserver.recordResolution(task, result);
817
825
  return result;
818
826
  }
819
827
 
@@ -873,12 +881,9 @@ export class LocalRuntime {
873
881
 
874
882
  terminateActiveProcesses(signal = "SIGTERM", escalate = false) {
875
883
  const children = [...this.activeProcesses];
876
- for (const child of children) terminateProcessTree(child, signal);
877
- if (escalate && signal !== "SIGKILL" && children.length) {
878
- const timer = setTimeout(() => {
879
- for (const child of children) if (this.activeProcesses.has(child)) terminateProcessTree(child, "SIGKILL");
880
- }, 2000);
881
- timer.unref?.();
884
+ for (const child of children) {
885
+ if (escalate && signal !== "SIGKILL") terminateProcessTreeWithEscalation(child);
886
+ else terminateProcessTree(child, signal);
882
887
  }
883
888
  }
884
889
 
@@ -911,14 +916,12 @@ export class LocalRuntime {
911
916
  let killTimer = null;
912
917
  const timer = setTimeout(() => {
913
918
  timedOut = true;
914
- terminateProcessTree(child, "SIGTERM");
915
- killTimer = setTimeout(() => terminateProcessTree(child, "SIGKILL"), 2000);
916
- killTimer.unref?.();
919
+ killTimer = terminateProcessTreeWithEscalation(child);
917
920
  }, timeoutMs);
918
921
  timer.unref?.();
919
922
  const cleanup = () => {
920
923
  clearTimeout(timer);
921
- if (killTimer) clearTimeout(killTimer);
924
+ if (killTimer && !timedOut) clearTimeout(killTimer);
922
925
  this.activeProcesses.delete(child);
923
926
  if (context.callId) {
924
927
  const set = this.callProcesses.get(context.callId);
@@ -57,7 +57,7 @@
57
57
  {
58
58
  "name": "resolve_task_capabilities",
59
59
  "title": "Resolve task capabilities",
60
- "description": "Rescan built-in and automatic project context, local instruction files, skills, registered commands, applications, and browser capability metadata; rank the capabilities relevant to the current task and optionally load the best skill.",
60
+ "description": "Rescan built-in and automatic project context, local instruction files, skills, explicit and automatic package commands, installed applications, and browser capability metadata; rank the capabilities relevant to the current task and optionally load the best skill.",
61
61
  "availability": "always",
62
62
  "annotations": {
63
63
  "readOnlyHint": true,
@@ -385,6 +385,55 @@
385
385
  }
386
386
  }
387
387
  },
388
+ {
389
+ "name": "browser_manage_tabs",
390
+ "title": "Manage browser tabs",
391
+ "description": "Create, activate, or close tabs in the paired existing browser profile.",
392
+ "availability": "full",
393
+ "annotations": {
394
+ "readOnlyHint": false,
395
+ "destructiveHint": true,
396
+ "idempotentHint": false,
397
+ "openWorldHint": true
398
+ },
399
+ "inputSchema": {
400
+ "type": "object",
401
+ "additionalProperties": false,
402
+ "properties": {
403
+ "action": {
404
+ "type": "string",
405
+ "enum": [
406
+ "new",
407
+ "activate",
408
+ "close"
409
+ ]
410
+ },
411
+ "tab_id": {
412
+ "type": "integer",
413
+ "minimum": 1,
414
+ "description": "Required for activate and close."
415
+ },
416
+ "url": {
417
+ "type": "string",
418
+ "maxLength": 32768,
419
+ "description": "Optional absolute http, https, or file URL for a new tab."
420
+ },
421
+ "active": {
422
+ "type": "boolean",
423
+ "default": true
424
+ },
425
+ "timeout_seconds": {
426
+ "type": "integer",
427
+ "minimum": 1,
428
+ "maximum": 120,
429
+ "default": 30
430
+ }
431
+ },
432
+ "required": [
433
+ "action"
434
+ ]
435
+ }
436
+ },
388
437
  {
389
438
  "name": "browser_get_source",
390
439
  "title": "Read browser page source",
@@ -473,10 +522,115 @@
473
522
  }
474
523
  }
475
524
  },
525
+ {
526
+ "name": "browser_wait",
527
+ "title": "Wait for browser state",
528
+ "description": "Wait until all supplied URL, load, text, and element-state conditions are satisfied in an existing browser tab.",
529
+ "availability": "full",
530
+ "annotations": {
531
+ "readOnlyHint": true,
532
+ "destructiveHint": false,
533
+ "idempotentHint": true,
534
+ "openWorldHint": true
535
+ },
536
+ "inputSchema": {
537
+ "type": "object",
538
+ "additionalProperties": false,
539
+ "properties": {
540
+ "tab_id": {
541
+ "type": "integer",
542
+ "minimum": 1
543
+ },
544
+ "frame_id": {
545
+ "type": "integer",
546
+ "minimum": 0
547
+ },
548
+ "selector": {
549
+ "type": "object",
550
+ "properties": {
551
+ "ref": {
552
+ "type": "string",
553
+ "maxLength": 100,
554
+ "description": "Stable element reference returned by browser_inspect_page for the same document and frame."
555
+ },
556
+ "css": {
557
+ "type": "string",
558
+ "maxLength": 2000
559
+ },
560
+ "id": {
561
+ "type": "string",
562
+ "maxLength": 2000
563
+ },
564
+ "name": {
565
+ "type": "string",
566
+ "maxLength": 2000
567
+ },
568
+ "label": {
569
+ "type": "string",
570
+ "maxLength": 2000
571
+ },
572
+ "text": {
573
+ "type": "string",
574
+ "maxLength": 2000
575
+ },
576
+ "role": {
577
+ "type": "string",
578
+ "maxLength": 2000
579
+ },
580
+ "placeholder": {
581
+ "type": "string",
582
+ "maxLength": 2000
583
+ },
584
+ "index": {
585
+ "type": "integer",
586
+ "minimum": 0,
587
+ "maximum": 10000
588
+ }
589
+ },
590
+ "additionalProperties": false
591
+ },
592
+ "state": {
593
+ "type": "string",
594
+ "enum": [
595
+ "attached",
596
+ "detached",
597
+ "visible",
598
+ "hidden",
599
+ "enabled",
600
+ "editable",
601
+ "checked",
602
+ "unchecked"
603
+ ],
604
+ "description": "Element state to wait for. Defaults to visible only when selector is supplied."
605
+ },
606
+ "text": {
607
+ "type": "string",
608
+ "maxLength": 4000
609
+ },
610
+ "url_contains": {
611
+ "type": "string",
612
+ "maxLength": 32768
613
+ },
614
+ "load_state": {
615
+ "type": "string",
616
+ "enum": [
617
+ "domcontentloaded",
618
+ "complete"
619
+ ]
620
+ },
621
+ "timeout_seconds": {
622
+ "type": "integer",
623
+ "minimum": 1,
624
+ "maximum": 120,
625
+ "default": 30
626
+ }
627
+ }
628
+ }
629
+ },
476
630
  {
477
631
  "name": "browser_action",
478
632
  "title": "Operate browser page",
479
- "description": "Perform one structured navigation or DOM action in the user's existing browser tab without accepting arbitrary JavaScript.",
633
+ "description": "Perform one structured navigation or page action in the user's existing browser tab, with automatic actionability waits and optional fixed DevTools input, without accepting arbitrary JavaScript.",
480
634
  "availability": "full",
481
635
  "annotations": {
482
636
  "readOnlyHint": false,
@@ -506,12 +660,21 @@
506
660
  "submit",
507
661
  "reload",
508
662
  "back",
509
- "forward"
663
+ "forward",
664
+ "double_click",
665
+ "hover",
666
+ "type_text",
667
+ "scroll_into_view"
510
668
  ]
511
669
  },
512
670
  "selector": {
513
671
  "type": "object",
514
672
  "properties": {
673
+ "ref": {
674
+ "type": "string",
675
+ "maxLength": 100,
676
+ "description": "Stable element reference returned by browser_inspect_page for the same document and frame."
677
+ },
515
678
  "css": {
516
679
  "type": "string",
517
680
  "maxLength": 2000
@@ -582,6 +745,22 @@
582
745
  "frame_id": {
583
746
  "type": "integer",
584
747
  "minimum": 0
748
+ },
749
+ "input_mode": {
750
+ "type": "string",
751
+ "enum": [
752
+ "auto",
753
+ "trusted",
754
+ "dom"
755
+ ],
756
+ "default": "auto",
757
+ "description": "Use fixed DevTools Input commands when possible, require them, or use DOM events only."
758
+ },
759
+ "element_timeout_seconds": {
760
+ "type": "integer",
761
+ "minimum": 1,
762
+ "maximum": 60,
763
+ "default": 10
585
764
  }
586
765
  },
587
766
  "required": [
@@ -618,6 +797,11 @@
618
797
  "selector": {
619
798
  "type": "object",
620
799
  "properties": {
800
+ "ref": {
801
+ "type": "string",
802
+ "maxLength": 100,
803
+ "description": "Stable element reference returned by browser_inspect_page for the same document and frame."
804
+ },
621
805
  "css": {
622
806
  "type": "string",
623
807
  "maxLength": 2000
@@ -691,6 +875,11 @@
691
875
  "submit_selector": {
692
876
  "type": "object",
693
877
  "properties": {
878
+ "ref": {
879
+ "type": "string",
880
+ "maxLength": 100,
881
+ "description": "Stable element reference returned by browser_inspect_page for the same document and frame."
882
+ },
694
883
  "css": {
695
884
  "type": "string",
696
885
  "maxLength": 2000
@@ -745,6 +934,12 @@
745
934
  "frame_id": {
746
935
  "type": "integer",
747
936
  "minimum": 0
937
+ },
938
+ "element_timeout_seconds": {
939
+ "type": "integer",
940
+ "minimum": 1,
941
+ "maximum": 60,
942
+ "default": 10
748
943
  }
749
944
  },
750
945
  "required": [
@@ -815,6 +1010,11 @@
815
1010
  "selector": {
816
1011
  "type": "object",
817
1012
  "properties": {
1013
+ "ref": {
1014
+ "type": "string",
1015
+ "maxLength": 100,
1016
+ "description": "Stable element reference returned by browser_inspect_page for the same document and frame."
1017
+ },
818
1018
  "css": {
819
1019
  "type": "string",
820
1020
  "maxLength": 2000
@@ -885,6 +1085,12 @@
885
1085
  "frame_id": {
886
1086
  "type": "integer",
887
1087
  "minimum": 0
1088
+ },
1089
+ "element_timeout_seconds": {
1090
+ "type": "integer",
1091
+ "minimum": 1,
1092
+ "maximum": 60,
1093
+ "default": 10
888
1094
  }
889
1095
  },
890
1096
  "required": [
@@ -897,7 +1103,7 @@
897
1103
  {
898
1104
  "name": "agent_context",
899
1105
  "title": "Load agent context",
900
- "description": "Discover built-in defaults, bounded automatic project facts, Codex-compatible global/root-to-target instruction precedence, progressively disclosed local skills, and registered commands for a target path.",
1106
+ "description": "Discover built-in defaults, bounded automatic project facts, Codex-compatible global/root-to-target instruction precedence, progressively disclosed local skills, explicit commands, and safe automatic package-script command aliases for a target path.",
901
1107
  "availability": "always",
902
1108
  "annotations": {
903
1109
  "readOnlyHint": true,
@@ -1001,7 +1207,7 @@
1001
1207
  {
1002
1208
  "name": "list_local_commands",
1003
1209
  "title": "List registered local commands",
1004
- "description": "List commands registered by hierarchical .machine-bridge/agent.json manifests for the selected target path.",
1210
+ "description": "List effective direct-argv commands from project manifests and safe automatic package-script aliases, including provenance and timeout limits.",
1005
1211
  "availability": "always",
1006
1212
  "annotations": {
1007
1213
  "readOnlyHint": true,
@@ -1024,7 +1230,7 @@
1024
1230
  {
1025
1231
  "name": "run_local_command",
1026
1232
  "title": "Run registered local command",
1027
- "description": "Execute a named command registered in .machine-bridge/agent.json as a direct argv process without shell parsing. Manifest timeout and argument policy remain authoritative.",
1233
+ "description": "Run an effective manifest or automatic package-script command through its fixed argv, cwd, timeout ceiling, and extra-argument policy.",
1028
1234
  "availability": "direct-exec",
1029
1235
  "annotations": {
1030
1236
  "readOnlyHint": false,
@@ -3,7 +3,7 @@ import toolCatalog from "../shared/tool-catalog.json";
3
3
  import serverMetadata from "../shared/server-metadata.json";
4
4
 
5
5
  const SERVER_NAME = String(serverMetadata.name);
6
- const SERVER_VERSION = "0.12.2";
6
+ const SERVER_VERSION = "0.14.0";
7
7
  const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
8
8
  const MCP_SUPPORTED_PROTOCOL_VERSIONS = serverMetadata.supportedProtocolVersions.map((value) => String(value));
9
9
  const JSONRPC_VERSION = "2.0";
@@ -817,6 +817,7 @@ export class BridgeRoom extends DurableObject<BridgeEnv> {
817
817
  client.last_used_at = now;
818
818
 
819
819
  const code = randomToken("mcp_code");
820
+ const redirectLocation = authorizationRedirectLocation(redirectUri, code, state);
820
821
  store.codes[code] = {
821
822
  client_id: clientId,
822
823
  redirect_uri: redirectUri,
@@ -829,9 +830,7 @@ export class BridgeRoom extends DurableObject<BridgeEnv> {
829
830
  pruneRecordByExpiry(store.codes, MAX_OAUTH_CODES);
830
831
  await this.ctx.storage.put("oauth", store);
831
832
 
832
- const params = new URLSearchParams({ code });
833
- if (state) params.set("state", state);
834
- return oauthRedirect(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}${params.toString()}`);
833
+ return oauthRedirect(redirectLocation);
835
834
  });
836
835
  }
837
836
 
@@ -996,11 +995,11 @@ function methodNotAllowed(allow: string): Response {
996
995
  });
997
996
  }
998
997
 
999
- function oauthRedirect(location: string): Response {
998
+ function oauthRedirect(location: URL): Response {
1000
999
  return new Response(null, {
1001
- status: 302,
1000
+ status: 303,
1002
1001
  headers: {
1003
- location,
1002
+ location: location.href,
1004
1003
  "cache-control": "no-store",
1005
1004
  "referrer-policy": "no-referrer",
1006
1005
  "x-content-type-options": "nosniff",
@@ -1008,6 +1007,13 @@ function oauthRedirect(location: string): Response {
1008
1007
  });
1009
1008
  }
1010
1009
 
1010
+ function authorizationRedirectLocation(redirectUri: string, code: string, state: string): URL {
1011
+ const location = new URL(redirectUri);
1012
+ location.searchParams.append("code", code);
1013
+ if (state) location.searchParams.append("state", state);
1014
+ return location;
1015
+ }
1016
+
1011
1017
  function isFreshDaemonCandidate(connectedAt: string): boolean {
1012
1018
  const timestamp = Date.parse(connectedAt);
1013
1019
  if (!Number.isFinite(timestamp)) return false;
@@ -1187,7 +1193,7 @@ function daemonToolTimeoutMs(name: string, args: Record<string, unknown>): numbe
1187
1193
  const configurable = new Set([
1188
1194
  "exec_command", "run_process", "run_local_command", "open_local_application",
1189
1195
  "inspect_local_application", "operate_local_application", "browser_list_tabs",
1190
- "browser_get_source", "browser_inspect_page", "browser_action", "browser_fill_form",
1196
+ "browser_manage_tabs", "browser_wait", "browser_get_source", "browser_inspect_page", "browser_action", "browser_fill_form",
1191
1197
  "browser_screenshot", "browser_upload_files",
1192
1198
  ]);
1193
1199
  if (!configurable.has(name)) return 60_000;