viyv-browser-mcp 0.7.2 → 0.7.4

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
@@ -88,6 +88,57 @@ var MCP_SERVER = {
88
88
  SOCKET_PATH_TEMPLATE: "/tmp/viyv-browser-{pid}.sock"
89
89
  };
90
90
 
91
+ // src/native-host-updater.ts
92
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, renameSync, unlinkSync } from "fs";
93
+ import { homedir } from "os";
94
+ import { resolve } from "path";
95
+ import { fileURLToPath } from "url";
96
+ var NATIVE_HOST_DIR = resolve(homedir(), ".viyv-browser", "native-host");
97
+ function getSourceBinaryPath() {
98
+ return fileURLToPath(import.meta.url);
99
+ }
100
+ function getNativeHostBinaryPath() {
101
+ return resolve(NATIVE_HOST_DIR, "index.js");
102
+ }
103
+ function isNativeHostDeployed() {
104
+ return existsSync(getNativeHostBinaryPath());
105
+ }
106
+ function syncNativeHostBinary() {
107
+ const source = getSourceBinaryPath();
108
+ if (!existsSync(source)) {
109
+ throw new Error(`Source binary not found: ${source}`);
110
+ }
111
+ mkdirSync(NATIVE_HOST_DIR, { recursive: true });
112
+ const target = getNativeHostBinaryPath();
113
+ const tmp = resolve(NATIVE_HOST_DIR, `index.js.tmp.${process.pid}`);
114
+ try {
115
+ copyFileSync(source, tmp);
116
+ chmodSync(tmp, 493);
117
+ renameSync(tmp, target);
118
+ } catch (err) {
119
+ try {
120
+ unlinkSync(tmp);
121
+ } catch {
122
+ }
123
+ throw err;
124
+ }
125
+ }
126
+ function shouldUpdateBridge(bridgeVersion) {
127
+ return compareSemver("0.7.4", bridgeVersion) > 0;
128
+ }
129
+ function getPackageVersion() {
130
+ return "0.7.4";
131
+ }
132
+ function compareSemver(a, b) {
133
+ const pa = a.split(".").map(Number);
134
+ const pb = b.split(".").map(Number);
135
+ for (let i = 0; i < 3; i++) {
136
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
137
+ if (diff !== 0) return diff;
138
+ }
139
+ return 0;
140
+ }
141
+
91
142
  // src/native-host/bridge-relay.ts
92
143
  import { createConnection } from "net";
93
144
 
@@ -382,6 +433,7 @@ function startBridge(options) {
382
433
  const msg = {
383
434
  type: "bridge_status",
384
435
  connected: true,
436
+ bridgeVersion: getPackageVersion(),
385
437
  chromeProfiles: getChromeProfilesList(),
386
438
  timestamp: Date.now()
387
439
  };
@@ -460,6 +512,28 @@ function startBridge(options) {
460
512
  } else if (type === "bridge_status" || type === "bridge_closing") {
461
513
  process.stderr.write(`${LOG_PREFIX2} Ignoring ${type} from MCP Server ${connId}
462
514
  `);
515
+ } else if (type === "bridge_shutdown_request") {
516
+ process.stderr.write(`${LOG_PREFIX2} Shutdown requested by MCP Server ${connId}
517
+ `);
518
+ const DRAIN_TIMEOUT = 5e3;
519
+ const drainAndExit = () => {
520
+ if (requestOrigin.size === 0) {
521
+ gracefulExit("Bridge shutdown requested for version update");
522
+ return;
523
+ }
524
+ process.stderr.write(
525
+ `${LOG_PREFIX2} Waiting for ${requestOrigin.size} pending request(s) to drain...
526
+ `
527
+ );
528
+ const start = Date.now();
529
+ const check = setInterval(() => {
530
+ if (requestOrigin.size === 0 || Date.now() - start > DRAIN_TIMEOUT) {
531
+ clearInterval(check);
532
+ gracefulExit("Bridge shutdown requested for version update");
533
+ }
534
+ }, 200);
535
+ };
536
+ drainAndExit();
463
537
  } else {
464
538
  sendToChrome(agentId ? getChromeForAgent(agentId) : primaryChromeId, message);
465
539
  }
@@ -684,6 +758,7 @@ function startBridge(options) {
684
758
  sendToMcp(connId, {
685
759
  type: "bridge_status",
686
760
  connected: true,
761
+ bridgeVersion: getPackageVersion(),
687
762
  chromeProfiles: getChromeProfilesList(),
688
763
  timestamp: Date.now()
689
764
  });
@@ -770,7 +845,7 @@ function startBridge(options) {
770
845
 
771
846
  // src/server.ts
772
847
  import { randomUUID as randomUUID3 } from "crypto";
773
- import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "fs";
848
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, statSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
774
849
  import http from "http";
775
850
  import { createConnection as createConnection2 } from "net";
776
851
  import { dirname } from "path";
@@ -949,8 +1024,15 @@ var TOPICS = {
949
1024
  - Don't use for: checking text values, verifying form state
950
1025
  - Do use for: layout verification, image content, charts
951
1026
 
1027
+ 6. indexed_page \u2192 Alternative text-based DOM representation with numbered interactive elements.
1028
+ - Returns [0]<button>Submit</>, [1]<input placeholder=Search/>, etc.
1029
+ - Use indexed_action(snapshotId, index, action) to interact by number
1030
+ - No screenshots needed \u2014 works with text-only LLMs, lower token cost
1031
+ - snapshotId expires on page change \u2014 call indexed_page again if stale
1032
+
952
1033
  Recommended workflow:
953
- page_outline \u2192 read_page(section) \u2192 find(specific element) \u2192 click/form_input`
1034
+ page_outline \u2192 read_page(section) \u2192 find(specific element) \u2192 click/form_input
1035
+ OR: indexed_page \u2192 indexed_action(click/type/select_option) for lower-cost automation`
954
1036
  },
955
1037
  interaction: {
956
1038
  title: "Interaction \u2014 How to interact with elements",
@@ -998,6 +1080,14 @@ file_upload \u2014 For uploading files or dropping files onto elements
998
1080
  - Don't click file inputs \u2014 use file_upload instead
999
1081
  - Best for: file inputs, canvas apps (Canva, Figma), custom drop zones
1000
1082
 
1083
+ indexed_action \u2014 Index-based interaction (alternative to ref-based tools above)
1084
+ - Requires snapshotId from indexed_page
1085
+ - Actions: click, type, select_option, hover, scroll_to
1086
+ - click/hover: CDP dispatch (trusted events, SPA compatible)
1087
+ - type: CDP click to focus + content script value setting
1088
+ - select_option: content script DOM manipulation
1089
+ - Best for: batch automation with text-only LLMs, lower token cost
1090
+
1001
1091
  Common patterns:
1002
1092
  Single field: find(input) \u2192 form_input(ref, value, submit: true)
1003
1093
  Multi-field form: read_page \u2192 form_fill([{ref, value}, ...], submit: true)
@@ -1006,7 +1096,8 @@ Common patterns:
1006
1096
  Dropdown: click(select ref) \u2192 key("ArrowDown ArrowDown Enter")
1007
1097
  Search: form_input(search input ref, query, submit: true)
1008
1098
  File upload: find(file input) \u2192 file_upload(ref, paths)
1009
- File drop: find(canvas) \u2192 file_upload(ref, paths) or file_upload(coordinate, paths)`
1099
+ File drop: find(canvas) \u2192 file_upload(ref, paths) or file_upload(coordinate, paths)
1100
+ Index-based: indexed_page \u2192 indexed_action(click, index=N) \u2192 wait_for(navigation)`
1010
1101
  },
1011
1102
  navigation: {
1012
1103
  title: "Navigation \u2014 How to navigate between pages",
@@ -1437,7 +1528,7 @@ var CLICK_RETURNS = `{
1437
1528
  clicked: true
1438
1529
  newTab?: { tabId, url } // only if target="_blank" link detected
1439
1530
  }`;
1440
- var CLICK_RELATED = ["form_input", "hover", "drag", "javascript_exec"];
1531
+ var CLICK_RELATED = ["form_input", "hover", "drag", "javascript_exec", "indexed_action"];
1441
1532
 
1442
1533
  // src/tools/core/drag.ts
1443
1534
  var DRAG_DESCRIPTION = `Drag from start coordinate to end coordinate.
@@ -1533,7 +1624,7 @@ var FORM_INPUT_RETURNS = `{
1533
1624
  navigated?: boolean // if page navigated after submit
1534
1625
  url?: string // new URL if navigated
1535
1626
  }`;
1536
- var FORM_INPUT_RELATED = ["click", "type", "find", "read_page"];
1627
+ var FORM_INPUT_RELATED = ["click", "type", "find", "read_page", "indexed_action"];
1537
1628
 
1538
1629
  // src/tools/core/get-page-text.ts
1539
1630
  var GET_PAGE_TEXT_DESCRIPTION = `Extract readable text from page. Use query for semantic filtering.
@@ -1553,6 +1644,66 @@ var GET_PAGE_TEXT_RETURNS = `// Without query:
1553
1644
  { sections: [{ index, tag, role, ariaLabel, heading, text, truncated }], truncated: boolean, charCount: number }`;
1554
1645
  var GET_PAGE_TEXT_RELATED = ["read_page", "find", "screenshot"];
1555
1646
 
1647
+ // src/tools/core/indexed-action.ts
1648
+ var INDEXED_ACTION_DESCRIPTION = `Interact with an element by its [index] from indexed_page. Actions: click, type, select_option, hover, scroll_to.
1649
+ Returns: { success, action, index }`;
1650
+ var INDEXED_ACTION_DETAIL = `Act on an indexed element from indexed_page output.
1651
+
1652
+ Actions:
1653
+ - click: Click the element. Supports modifier keys (ctrl, shift, alt, meta).
1654
+ - type: Set text value on input/textarea/contenteditable. Clears existing content.
1655
+ - select_option: Select a dropdown option by text match.
1656
+ - hover: Hover over the element.
1657
+ - scroll_to: Scroll the element into view.
1658
+
1659
+ The snapshotId from indexed_page is required. If the page has changed since indexed_page was called, this tool returns an error \u2014 call indexed_page again to get a fresh snapshot.
1660
+
1661
+ Examples:
1662
+ indexed_action(tabId=1, snapshotId=3, index=2, action="click")
1663
+ indexed_action(tabId=1, snapshotId=3, index=1, action="type", text="hello")
1664
+ indexed_action(tabId=1, snapshotId=3, index=5, action="select_option", option="Option A")`;
1665
+ var INDEXED_ACTION_RETURNS = `{
1666
+ action: string // the action performed
1667
+ index: number // the element index
1668
+ success: boolean // whether the action succeeded
1669
+ value?: string // set value (for type/select_option)
1670
+ tagName?: string // element tag name
1671
+ }`;
1672
+ var INDEXED_ACTION_RELATED = ["indexed_page", "click", "form_input"];
1673
+
1674
+ // src/tools/core/indexed-page.ts
1675
+ var INDEXED_PAGE_DESCRIPTION = `Get page DOM as indexed text. Interactive elements marked [0], [1], etc. Use indexed_action to act by index.
1676
+ Returns: { tree, snapshotId, elementCount, interactiveCount }`;
1677
+ var INDEXED_PAGE_DETAIL = `Alternative to read_page that returns a text-based DOM representation with numbered interactive elements.
1678
+
1679
+ Format example:
1680
+ [0]<a aria-label=Home>Home />
1681
+ [1]<input placeholder=Search />
1682
+ [2]<button>Submit />
1683
+ Some text content
1684
+ [3]<a>Learn more />
1685
+
1686
+ Each [N] marks an interactive element. Use indexed_action with the index number to click, type, or interact.
1687
+
1688
+ Options:
1689
+ - viewportOnly: only include elements visible in the viewport (default: false = full page)
1690
+ - maxChars: limit output size (default 50000)
1691
+ - includeAttributes: additional HTML attributes to include in output
1692
+
1693
+ The returned snapshotId must be passed to indexed_action. It expires after page changes \u2014 call indexed_page again if indexed_action reports a stale snapshot.
1694
+
1695
+ Advantages over read_page:
1696
+ - Lower token cost (no screenshots needed)
1697
+ - Works with text-only LLMs
1698
+ - Direct index-based interaction without ref resolution`;
1699
+ var INDEXED_PAGE_RETURNS = `{
1700
+ tree: string // indexed DOM text
1701
+ snapshotId: number // pass to indexed_action
1702
+ elementCount: number // total elements
1703
+ interactiveCount: number // indexed interactive elements
1704
+ }`;
1705
+ var INDEXED_PAGE_RELATED = ["indexed_action", "read_page", "screenshot"];
1706
+
1556
1707
  // src/tools/core/handle-dialog.ts
1557
1708
  var HANDLE_DIALOG_DESCRIPTION = `Handle JS dialog (alert/confirm/prompt) or set next auto-handle policy.
1558
1709
  Returns: { handled } or { policy_set }`;
@@ -1687,7 +1838,7 @@ var PAGE_OUTLINE_RETURNS = `{
1687
1838
  }]
1688
1839
  note?: string // only if no landmarks found
1689
1840
  }`;
1690
- var PAGE_OUTLINE_RELATED = ["read_page", "find", "get_page_text"];
1841
+ var PAGE_OUTLINE_RELATED = ["read_page", "find", "get_page_text", "indexed_page"];
1691
1842
 
1692
1843
  // src/tools/core/read-page.ts
1693
1844
  var READ_PAGE_DESCRIPTION = `Get page accessibility tree with element refs for interaction. Call page_outline first on unfamiliar pages.
@@ -1712,7 +1863,7 @@ var READ_PAGE_RETURNS = `{
1712
1863
  elementCount: number // total elements in tree
1713
1864
  truncated: boolean // whether output hit maxChars limit
1714
1865
  }`;
1715
- var READ_PAGE_RELATED = ["page_outline", "find", "get_page_text", "screenshot"];
1866
+ var READ_PAGE_RELATED = ["page_outline", "find", "get_page_text", "screenshot", "indexed_page"];
1716
1867
 
1717
1868
  // src/tools/core/read-table.ts
1718
1869
  var READ_TABLE_DESCRIPTION = `Extract HTML table data from web page by ref. Supports <table> and ARIA role="table".
@@ -3361,6 +3512,37 @@ var readPageTool = {
3361
3512
  )
3362
3513
  })
3363
3514
  };
3515
+ var indexedPageTool = {
3516
+ name: "indexed_page",
3517
+ description: INDEXED_PAGE_DESCRIPTION,
3518
+ detail: INDEXED_PAGE_DETAIL,
3519
+ returns: INDEXED_PAGE_RETURNS,
3520
+ category: "core",
3521
+ related: INDEXED_PAGE_RELATED,
3522
+ inputSchema: z.object({
3523
+ tabId: z.coerce.number().describe("Tab ID"),
3524
+ viewportOnly: z.boolean().optional().describe("Only include viewport-visible elements (default: false)"),
3525
+ maxChars: z.coerce.number().optional().describe("Max output characters (default: 50000)"),
3526
+ includeAttributes: z.array(z.string()).optional().describe("Additional HTML attributes to include in output")
3527
+ })
3528
+ };
3529
+ var indexedActionTool = {
3530
+ name: "indexed_action",
3531
+ description: INDEXED_ACTION_DESCRIPTION,
3532
+ detail: INDEXED_ACTION_DETAIL,
3533
+ returns: INDEXED_ACTION_RETURNS,
3534
+ category: "core",
3535
+ related: INDEXED_ACTION_RELATED,
3536
+ inputSchema: z.object({
3537
+ tabId: z.coerce.number().describe("Tab ID"),
3538
+ snapshotId: z.coerce.number().describe("Snapshot ID from indexed_page"),
3539
+ index: z.coerce.number().min(0).describe("Element index from indexed_page output"),
3540
+ action: z.enum(["click", "type", "select_option", "hover", "scroll_to"]).describe("Action to perform on the element"),
3541
+ text: z.string().optional().describe('Text to type (required for action: "type")'),
3542
+ option: z.string().optional().describe('Option text to select (required for action: "select_option")'),
3543
+ modifiers: z.string().optional().describe('Modifier keys for click (e.g. "ctrl+shift")')
3544
+ })
3545
+ };
3364
3546
  var findTool = {
3365
3547
  name: "find",
3366
3548
  description: FIND_DESCRIPTION,
@@ -4711,6 +4893,8 @@ var allTools = [
4711
4893
  hoverTool,
4712
4894
  dragTool,
4713
4895
  readPageTool,
4896
+ indexedPageTool,
4897
+ indexedActionTool,
4714
4898
  findTool,
4715
4899
  inspectTool,
4716
4900
  formInputTool,
@@ -4846,6 +5030,7 @@ function computeToolTimeout(tool, input) {
4846
5030
  // src/server.ts
4847
5031
  var pendingRequests = /* @__PURE__ */ new Map();
4848
5032
  var extensionSocket = null;
5033
+ var bridgeUpdateInfo = null;
4849
5034
  var configuredChromeProfile;
4850
5035
  function coerceValue(v) {
4851
5036
  if (typeof v !== "string") return v;
@@ -4867,7 +5052,7 @@ function coerceShape(shape) {
4867
5052
  return result;
4868
5053
  }
4869
5054
  function handleFileExport(filePath, result) {
4870
- mkdirSync(dirname(filePath), { recursive: true });
5055
+ mkdirSync2(dirname(filePath), { recursive: true });
4871
5056
  let content;
4872
5057
  let metadata;
4873
5058
  if (typeof result.data === "string") {
@@ -4931,6 +5116,14 @@ function createConfiguredMcpServer() {
4931
5116
  } catch {
4932
5117
  }
4933
5118
  }
5119
+ if (bridgeUpdateInfo) {
5120
+ const info = bridgeUpdateInfo;
5121
+ bridgeUpdateInfo = null;
5122
+ result.content.unshift({
5123
+ type: "text",
5124
+ text: `[INFO] Bridge was restarted for version update (${info.from} \u2192 ${info.to}). Connection re-established.`
5125
+ });
5126
+ }
4934
5127
  return result;
4935
5128
  });
4936
5129
  }
@@ -5221,7 +5414,7 @@ async function handleStreamableHttpRequest(req, res, sessions2) {
5221
5414
  }
5222
5415
  var MAX_BODY_SIZE = 4 * 1024 * 1024;
5223
5416
  function parseJsonBody(req) {
5224
- return new Promise((resolve2, reject) => {
5417
+ return new Promise((resolve3, reject) => {
5225
5418
  const chunks = [];
5226
5419
  let size = 0;
5227
5420
  let destroyed = false;
@@ -5239,7 +5432,7 @@ function parseJsonBody(req) {
5239
5432
  req.on("end", () => {
5240
5433
  if (destroyed) return;
5241
5434
  try {
5242
- resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
5435
+ resolve3(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
5243
5436
  } catch (error) {
5244
5437
  reject(new Error(`Invalid JSON: ${error.message}`));
5245
5438
  }
@@ -5447,6 +5640,21 @@ function handleExtensionMessage(message) {
5447
5640
  }
5448
5641
  }
5449
5642
  }
5643
+ const bridgeVersion = typeof msg.bridgeVersion === "string" ? msg.bridgeVersion : null;
5644
+ if (bridgeVersion && shouldUpdateBridge(bridgeVersion)) {
5645
+ const serverVersion = getPackageVersion();
5646
+ process.stderr.write(
5647
+ `[viyv-browser:mcp] Bridge version mismatch (bridge=${bridgeVersion}, server=${serverVersion}), requesting restart
5648
+ `
5649
+ );
5650
+ bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
5651
+ if (extensionSocket && !extensionSocket.destroyed) {
5652
+ extensionSocket.write(
5653
+ `${JSON.stringify({ type: "bridge_shutdown_request", timestamp: Date.now() })}
5654
+ `
5655
+ );
5656
+ }
5657
+ }
5450
5658
  } else if (type === "browser_event") {
5451
5659
  process.stderr.write(`[viyv-browser:mcp] Browser event: ${String(msg.eventType)}
5452
5660
  `);
@@ -5584,7 +5792,7 @@ async function callExtensionTool(tool, input) {
5584
5792
  if (paths) {
5585
5793
  for (const p of paths) {
5586
5794
  try {
5587
- if (!existsSync(p) || !statSync(p).isFile()) {
5795
+ if (!existsSync2(p) || !statSync(p).isFile()) {
5588
5796
  return {
5589
5797
  content: [
5590
5798
  {
@@ -5616,7 +5824,7 @@ async function callExtensionTool(tool, input) {
5616
5824
  let csvData;
5617
5825
  if (typeof input.file_path === "string") {
5618
5826
  const fp = input.file_path;
5619
- if (!existsSync(fp) || !statSync(fp).isFile()) {
5827
+ if (!existsSync2(fp) || !statSync(fp).isFile()) {
5620
5828
  return {
5621
5829
  content: [
5622
5830
  {
@@ -5674,13 +5882,13 @@ async function callExtensionTool(tool, input) {
5674
5882
  touchSession(agentId);
5675
5883
  const sock = extensionSocket;
5676
5884
  const toolTimeout = computeToolTimeout(tool, input);
5677
- return new Promise((resolve2) => {
5885
+ return new Promise((resolve3) => {
5678
5886
  const onError = () => {
5679
5887
  const pending = pendingRequests.get(requestId);
5680
5888
  if (pending) {
5681
5889
  clearTimeout(pending.timer);
5682
5890
  pendingRequests.delete(requestId);
5683
- resolve2({
5891
+ resolve3({
5684
5892
  content: [
5685
5893
  {
5686
5894
  type: "text",
@@ -5701,7 +5909,7 @@ async function callExtensionTool(tool, input) {
5701
5909
  const timer = setTimeout(() => {
5702
5910
  pendingRequests.delete(requestId);
5703
5911
  removeErrorListener();
5704
- resolve2({
5912
+ resolve3({
5705
5913
  content: [
5706
5914
  {
5707
5915
  type: "text",
@@ -5726,12 +5934,12 @@ async function callExtensionTool(tool, input) {
5726
5934
  if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
5727
5935
  try {
5728
5936
  const metadata = handleFileExport(filePath, result);
5729
- resolve2({
5937
+ resolve3({
5730
5938
  content: [{ type: "text", text: JSON.stringify(metadata) }]
5731
5939
  });
5732
5940
  } catch (e) {
5733
5941
  const msg = e instanceof Error ? e.message : String(e);
5734
- resolve2({
5942
+ resolve3({
5735
5943
  content: [
5736
5944
  {
5737
5945
  type: "text",
@@ -5750,9 +5958,9 @@ async function callExtensionTool(tool, input) {
5750
5958
  if (RESULT_EXPORT_TOOLS.has(tool) && typeof filePath === "string") {
5751
5959
  try {
5752
5960
  const content = JSON.stringify(result, null, 2);
5753
- mkdirSync(dirname(filePath), { recursive: true });
5961
+ mkdirSync2(dirname(filePath), { recursive: true });
5754
5962
  writeFileSync(filePath, content, "utf-8");
5755
- resolve2({
5963
+ resolve3({
5756
5964
  content: [
5757
5965
  {
5758
5966
  type: "text",
@@ -5765,7 +5973,7 @@ async function callExtensionTool(tool, input) {
5765
5973
  });
5766
5974
  } catch (e) {
5767
5975
  const msg = e instanceof Error ? e.message : String(e);
5768
- resolve2({
5976
+ resolve3({
5769
5977
  content: [
5770
5978
  {
5771
5979
  type: "text",
@@ -5792,21 +6000,21 @@ async function callExtensionTool(tool, input) {
5792
6000
  }
5793
6001
  if (tempImportFile) {
5794
6002
  try {
5795
- unlinkSync(tempImportFile);
6003
+ unlinkSync2(tempImportFile);
5796
6004
  } catch {
5797
6005
  }
5798
6006
  }
5799
- resolve2({ content: buildResponseContent(tool, finalResult) });
6007
+ resolve3({ content: buildResponseContent(tool, finalResult) });
5800
6008
  },
5801
6009
  reject: (error) => {
5802
6010
  removeErrorListener();
5803
6011
  if (tempImportFile) {
5804
6012
  try {
5805
- unlinkSync(tempImportFile);
6013
+ unlinkSync2(tempImportFile);
5806
6014
  } catch {
5807
6015
  }
5808
6016
  }
5809
- resolve2({
6017
+ resolve3({
5810
6018
  content: [
5811
6019
  {
5812
6020
  type: "text",
@@ -5858,12 +6066,12 @@ async function handleSwitchBrowser() {
5858
6066
  setExtensionConnected(false);
5859
6067
  }
5860
6068
  process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
5861
- return new Promise((resolve2) => {
6069
+ return new Promise((resolve3) => {
5862
6070
  const checkInterval = setInterval(() => {
5863
6071
  if (extensionSocket && !extensionSocket.destroyed && isExtensionConnected()) {
5864
6072
  clearInterval(checkInterval);
5865
6073
  clearTimeout(timer);
5866
- resolve2({
6074
+ resolve3({
5867
6075
  content: [
5868
6076
  {
5869
6077
  type: "text",
@@ -5878,7 +6086,7 @@ async function handleSwitchBrowser() {
5878
6086
  }, 500);
5879
6087
  const timer = setTimeout(() => {
5880
6088
  clearInterval(checkInterval);
5881
- resolve2({
6089
+ resolve3({
5882
6090
  content: [
5883
6091
  {
5884
6092
  type: "text",
@@ -5897,27 +6105,24 @@ async function handleSwitchBrowser() {
5897
6105
 
5898
6106
  // src/setup.ts
5899
6107
  import { execSync } from "child_process";
5900
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5901
- import { homedir, platform } from "os";
5902
- import { dirname as dirname2, resolve } from "path";
6108
+ import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
6109
+ import { homedir as homedir2, platform } from "os";
6110
+ import { dirname as dirname2, resolve as resolve2 } from "path";
5903
6111
  function runSetup(options = {}) {
5904
6112
  const os = platform();
5905
- const binaryPath = getBinaryPath();
5906
6113
  console.log("Viyv Browser MCP - Native Messaging Host Setup");
5907
6114
  console.log("================================================");
5908
6115
  console.log(`Platform: ${os}`);
5909
- console.log(`Binary: ${binaryPath}`);
5910
- if (!existsSync2(binaryPath)) {
5911
- console.error(`WARNING: Binary not found at ${binaryPath}`);
5912
- console.error("The Native Messaging Host may not work until the binary is available.");
5913
- }
5914
6116
  const allowedOrigins = options.extensionId ? [`chrome-extension://${options.extensionId}/`] : ["chrome-extension://*/"];
5915
6117
  if (!options.extensionId) {
5916
6118
  process.stderr.write(
5917
6119
  "WARNING: Using wildcard allowed_origins (chrome-extension://*/). This allows any Chrome extension to connect. For production, specify --extension-id to restrict access.\n"
5918
6120
  );
5919
6121
  }
5920
- const wrapperPath = createNativeHostWrapper(os, binaryPath);
6122
+ syncNativeHostBinary();
6123
+ const nativeHostPath = getNativeHostBinaryPath();
6124
+ console.log(`Native host binary: ${nativeHostPath}`);
6125
+ const wrapperPath = createNativeHostWrapper(os, nativeHostPath);
5921
6126
  const manifest = {
5922
6127
  name: NATIVE_HOST_NAME,
5923
6128
  description: "Viyv Browser MCP Native Messaging Host",
@@ -5929,9 +6134,9 @@ function runSetup(options = {}) {
5929
6134
  const manifestDir = dirname2(manifestPath);
5930
6135
  console.log(`Wrapper: ${wrapperPath}`);
5931
6136
  console.log(`Manifest path: ${manifestPath}`);
5932
- mkdirSync2(manifestDir, { recursive: true });
6137
+ mkdirSync3(manifestDir, { recursive: true });
5933
6138
  writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
5934
- chmodSync(manifestPath, 420);
6139
+ chmodSync2(manifestPath, 420);
5935
6140
  console.log("\nNative Messaging Host registered successfully!");
5936
6141
  console.log("\nNext steps:");
5937
6142
  console.log("1. Start the MCP Server: node <path>/dist/index.js");
@@ -5941,32 +6146,22 @@ function runSetup(options = {}) {
5941
6146
  setupClaudeDesktopConfig();
5942
6147
  }
5943
6148
  }
5944
- function getBinaryPath() {
5945
- const whichCmd = process.platform === "win32" ? "where" : "which";
5946
- try {
5947
- const found = execSync(`${whichCmd} viyv-browser-mcp`, { encoding: "utf-8" }).trim();
5948
- if (found) return found;
5949
- } catch {
5950
- }
5951
- const currentScript = process.argv[1];
5952
- return resolve(currentScript);
5953
- }
5954
6149
  function createNativeHostWrapper(os, binaryPath) {
5955
6150
  const manifestDir = dirname2(getManifestPath(os));
5956
- mkdirSync2(manifestDir, { recursive: true });
6151
+ mkdirSync3(manifestDir, { recursive: true });
5957
6152
  const nodePath = getNodePath();
5958
6153
  if (os === "win32") {
5959
- const wrapperPath2 = resolve(manifestDir, `${NATIVE_HOST_NAME}.bat`);
6154
+ const wrapperPath2 = resolve2(manifestDir, `${NATIVE_HOST_NAME}.bat`);
5960
6155
  writeFileSync2(wrapperPath2, `@echo off\r
5961
6156
  "${nodePath}" "${binaryPath}" --native-host\r
5962
6157
  `);
5963
6158
  return wrapperPath2;
5964
6159
  }
5965
- const wrapperPath = resolve(manifestDir, `${NATIVE_HOST_NAME}.sh`);
6160
+ const wrapperPath = resolve2(manifestDir, `${NATIVE_HOST_NAME}.sh`);
5966
6161
  writeFileSync2(wrapperPath, `#!/bin/bash
5967
6162
  exec "${nodePath}" "${binaryPath}" --native-host
5968
6163
  `);
5969
- chmodSync(wrapperPath, 493);
6164
+ chmodSync2(wrapperPath, 493);
5970
6165
  return wrapperPath;
5971
6166
  }
5972
6167
  function getNodePath() {
@@ -5977,18 +6172,18 @@ function getNodePath() {
5977
6172
  }
5978
6173
  }
5979
6174
  function getManifestPath(os) {
5980
- const home = homedir();
6175
+ const home = homedir2();
5981
6176
  switch (os) {
5982
6177
  case "darwin":
5983
- return resolve(
6178
+ return resolve2(
5984
6179
  home,
5985
6180
  "Library/Application Support/Google/Chrome/NativeMessagingHosts",
5986
6181
  `${NATIVE_HOST_NAME}.json`
5987
6182
  );
5988
6183
  case "linux":
5989
- return resolve(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
6184
+ return resolve2(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
5990
6185
  case "win32":
5991
- return resolve(
6186
+ return resolve2(
5992
6187
  home,
5993
6188
  "AppData/Local/Google/Chrome/User Data/NativeMessagingHosts",
5994
6189
  `${NATIVE_HOST_NAME}.json`
@@ -6008,7 +6203,7 @@ function setupClaudeDesktopConfig() {
6008
6203
  const configPath = getClaudeDesktopConfigPath();
6009
6204
  console.log(`Config path: ${configPath}`);
6010
6205
  let config = {};
6011
- if (existsSync2(configPath)) {
6206
+ if (existsSync3(configPath)) {
6012
6207
  try {
6013
6208
  config = JSON.parse(readFileSync2(configPath, "utf-8"));
6014
6209
  } catch {
@@ -6030,7 +6225,7 @@ function setupClaudeDesktopConfig() {
6030
6225
  env: { PATH: envPath }
6031
6226
  };
6032
6227
  const configDir = dirname2(configPath);
6033
- mkdirSync2(configDir, { recursive: true });
6228
+ mkdirSync3(configDir, { recursive: true });
6034
6229
  writeFileSync2(configPath, JSON.stringify(config, null, 2));
6035
6230
  console.log(`npx path: ${npxPath}`);
6036
6231
  console.log("\nClaude Desktop config updated successfully!");
@@ -6045,16 +6240,16 @@ function getNpxPath() {
6045
6240
  }
6046
6241
  }
6047
6242
  function getClaudeDesktopConfigPath() {
6048
- const home = homedir();
6243
+ const home = homedir2();
6049
6244
  switch (process.platform) {
6050
6245
  case "darwin":
6051
- return resolve(home, "Library/Application Support/Claude/claude_desktop_config.json");
6246
+ return resolve2(home, "Library/Application Support/Claude/claude_desktop_config.json");
6052
6247
  case "linux":
6053
- return resolve(home, ".config/Claude/claude_desktop_config.json");
6248
+ return resolve2(home, ".config/Claude/claude_desktop_config.json");
6054
6249
  case "win32":
6055
- return resolve(home, "AppData/Roaming/Claude/claude_desktop_config.json");
6250
+ return resolve2(home, "AppData/Roaming/Claude/claude_desktop_config.json");
6056
6251
  default:
6057
- return resolve(home, ".config/Claude/claude_desktop_config.json");
6252
+ return resolve2(home, ".config/Claude/claude_desktop_config.json");
6058
6253
  }
6059
6254
  }
6060
6255
 
@@ -6089,6 +6284,16 @@ if (args.includes("setup")) {
6089
6284
  const port = portIdx >= 0 ? Number(args[portIdx + 1]) : void 0;
6090
6285
  const chromeProfileIdx = args.indexOf("--chrome-profile");
6091
6286
  const chromeProfile = chromeProfileIdx >= 0 ? args[chromeProfileIdx + 1] : process.env.VIYV_CHROME_PROFILE;
6287
+ if (isNativeHostDeployed()) {
6288
+ try {
6289
+ syncNativeHostBinary();
6290
+ } catch (err) {
6291
+ process.stderr.write(
6292
+ `[viyv-browser:mcp] WARNING: Failed to update native host binary: ${err.message}
6293
+ `
6294
+ );
6295
+ }
6296
+ }
6092
6297
  startMcpServer(agentName, { transport: transportMode, port, chromeProfile });
6093
6298
  }
6094
6299
  //# sourceMappingURL=index.js.map