viyv-browser-mcp 0.7.2 → 0.7.3

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.3", bridgeVersion) > 0;
128
+ }
129
+ function getPackageVersion() {
130
+ return "0.7.3";
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";
@@ -4846,6 +4921,7 @@ function computeToolTimeout(tool, input) {
4846
4921
  // src/server.ts
4847
4922
  var pendingRequests = /* @__PURE__ */ new Map();
4848
4923
  var extensionSocket = null;
4924
+ var bridgeUpdateInfo = null;
4849
4925
  var configuredChromeProfile;
4850
4926
  function coerceValue(v) {
4851
4927
  if (typeof v !== "string") return v;
@@ -4867,7 +4943,7 @@ function coerceShape(shape) {
4867
4943
  return result;
4868
4944
  }
4869
4945
  function handleFileExport(filePath, result) {
4870
- mkdirSync(dirname(filePath), { recursive: true });
4946
+ mkdirSync2(dirname(filePath), { recursive: true });
4871
4947
  let content;
4872
4948
  let metadata;
4873
4949
  if (typeof result.data === "string") {
@@ -4931,6 +5007,14 @@ function createConfiguredMcpServer() {
4931
5007
  } catch {
4932
5008
  }
4933
5009
  }
5010
+ if (bridgeUpdateInfo) {
5011
+ const info = bridgeUpdateInfo;
5012
+ bridgeUpdateInfo = null;
5013
+ result.content.unshift({
5014
+ type: "text",
5015
+ text: `[INFO] Bridge was restarted for version update (${info.from} \u2192 ${info.to}). Connection re-established.`
5016
+ });
5017
+ }
4934
5018
  return result;
4935
5019
  });
4936
5020
  }
@@ -5221,7 +5305,7 @@ async function handleStreamableHttpRequest(req, res, sessions2) {
5221
5305
  }
5222
5306
  var MAX_BODY_SIZE = 4 * 1024 * 1024;
5223
5307
  function parseJsonBody(req) {
5224
- return new Promise((resolve2, reject) => {
5308
+ return new Promise((resolve3, reject) => {
5225
5309
  const chunks = [];
5226
5310
  let size = 0;
5227
5311
  let destroyed = false;
@@ -5239,7 +5323,7 @@ function parseJsonBody(req) {
5239
5323
  req.on("end", () => {
5240
5324
  if (destroyed) return;
5241
5325
  try {
5242
- resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
5326
+ resolve3(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
5243
5327
  } catch (error) {
5244
5328
  reject(new Error(`Invalid JSON: ${error.message}`));
5245
5329
  }
@@ -5447,6 +5531,21 @@ function handleExtensionMessage(message) {
5447
5531
  }
5448
5532
  }
5449
5533
  }
5534
+ const bridgeVersion = typeof msg.bridgeVersion === "string" ? msg.bridgeVersion : null;
5535
+ if (bridgeVersion && shouldUpdateBridge(bridgeVersion)) {
5536
+ const serverVersion = getPackageVersion();
5537
+ process.stderr.write(
5538
+ `[viyv-browser:mcp] Bridge version mismatch (bridge=${bridgeVersion}, server=${serverVersion}), requesting restart
5539
+ `
5540
+ );
5541
+ bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
5542
+ if (extensionSocket && !extensionSocket.destroyed) {
5543
+ extensionSocket.write(
5544
+ `${JSON.stringify({ type: "bridge_shutdown_request", timestamp: Date.now() })}
5545
+ `
5546
+ );
5547
+ }
5548
+ }
5450
5549
  } else if (type === "browser_event") {
5451
5550
  process.stderr.write(`[viyv-browser:mcp] Browser event: ${String(msg.eventType)}
5452
5551
  `);
@@ -5584,7 +5683,7 @@ async function callExtensionTool(tool, input) {
5584
5683
  if (paths) {
5585
5684
  for (const p of paths) {
5586
5685
  try {
5587
- if (!existsSync(p) || !statSync(p).isFile()) {
5686
+ if (!existsSync2(p) || !statSync(p).isFile()) {
5588
5687
  return {
5589
5688
  content: [
5590
5689
  {
@@ -5616,7 +5715,7 @@ async function callExtensionTool(tool, input) {
5616
5715
  let csvData;
5617
5716
  if (typeof input.file_path === "string") {
5618
5717
  const fp = input.file_path;
5619
- if (!existsSync(fp) || !statSync(fp).isFile()) {
5718
+ if (!existsSync2(fp) || !statSync(fp).isFile()) {
5620
5719
  return {
5621
5720
  content: [
5622
5721
  {
@@ -5674,13 +5773,13 @@ async function callExtensionTool(tool, input) {
5674
5773
  touchSession(agentId);
5675
5774
  const sock = extensionSocket;
5676
5775
  const toolTimeout = computeToolTimeout(tool, input);
5677
- return new Promise((resolve2) => {
5776
+ return new Promise((resolve3) => {
5678
5777
  const onError = () => {
5679
5778
  const pending = pendingRequests.get(requestId);
5680
5779
  if (pending) {
5681
5780
  clearTimeout(pending.timer);
5682
5781
  pendingRequests.delete(requestId);
5683
- resolve2({
5782
+ resolve3({
5684
5783
  content: [
5685
5784
  {
5686
5785
  type: "text",
@@ -5701,7 +5800,7 @@ async function callExtensionTool(tool, input) {
5701
5800
  const timer = setTimeout(() => {
5702
5801
  pendingRequests.delete(requestId);
5703
5802
  removeErrorListener();
5704
- resolve2({
5803
+ resolve3({
5705
5804
  content: [
5706
5805
  {
5707
5806
  type: "text",
@@ -5726,12 +5825,12 @@ async function callExtensionTool(tool, input) {
5726
5825
  if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
5727
5826
  try {
5728
5827
  const metadata = handleFileExport(filePath, result);
5729
- resolve2({
5828
+ resolve3({
5730
5829
  content: [{ type: "text", text: JSON.stringify(metadata) }]
5731
5830
  });
5732
5831
  } catch (e) {
5733
5832
  const msg = e instanceof Error ? e.message : String(e);
5734
- resolve2({
5833
+ resolve3({
5735
5834
  content: [
5736
5835
  {
5737
5836
  type: "text",
@@ -5750,9 +5849,9 @@ async function callExtensionTool(tool, input) {
5750
5849
  if (RESULT_EXPORT_TOOLS.has(tool) && typeof filePath === "string") {
5751
5850
  try {
5752
5851
  const content = JSON.stringify(result, null, 2);
5753
- mkdirSync(dirname(filePath), { recursive: true });
5852
+ mkdirSync2(dirname(filePath), { recursive: true });
5754
5853
  writeFileSync(filePath, content, "utf-8");
5755
- resolve2({
5854
+ resolve3({
5756
5855
  content: [
5757
5856
  {
5758
5857
  type: "text",
@@ -5765,7 +5864,7 @@ async function callExtensionTool(tool, input) {
5765
5864
  });
5766
5865
  } catch (e) {
5767
5866
  const msg = e instanceof Error ? e.message : String(e);
5768
- resolve2({
5867
+ resolve3({
5769
5868
  content: [
5770
5869
  {
5771
5870
  type: "text",
@@ -5792,21 +5891,21 @@ async function callExtensionTool(tool, input) {
5792
5891
  }
5793
5892
  if (tempImportFile) {
5794
5893
  try {
5795
- unlinkSync(tempImportFile);
5894
+ unlinkSync2(tempImportFile);
5796
5895
  } catch {
5797
5896
  }
5798
5897
  }
5799
- resolve2({ content: buildResponseContent(tool, finalResult) });
5898
+ resolve3({ content: buildResponseContent(tool, finalResult) });
5800
5899
  },
5801
5900
  reject: (error) => {
5802
5901
  removeErrorListener();
5803
5902
  if (tempImportFile) {
5804
5903
  try {
5805
- unlinkSync(tempImportFile);
5904
+ unlinkSync2(tempImportFile);
5806
5905
  } catch {
5807
5906
  }
5808
5907
  }
5809
- resolve2({
5908
+ resolve3({
5810
5909
  content: [
5811
5910
  {
5812
5911
  type: "text",
@@ -5858,12 +5957,12 @@ async function handleSwitchBrowser() {
5858
5957
  setExtensionConnected(false);
5859
5958
  }
5860
5959
  process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
5861
- return new Promise((resolve2) => {
5960
+ return new Promise((resolve3) => {
5862
5961
  const checkInterval = setInterval(() => {
5863
5962
  if (extensionSocket && !extensionSocket.destroyed && isExtensionConnected()) {
5864
5963
  clearInterval(checkInterval);
5865
5964
  clearTimeout(timer);
5866
- resolve2({
5965
+ resolve3({
5867
5966
  content: [
5868
5967
  {
5869
5968
  type: "text",
@@ -5878,7 +5977,7 @@ async function handleSwitchBrowser() {
5878
5977
  }, 500);
5879
5978
  const timer = setTimeout(() => {
5880
5979
  clearInterval(checkInterval);
5881
- resolve2({
5980
+ resolve3({
5882
5981
  content: [
5883
5982
  {
5884
5983
  type: "text",
@@ -5897,27 +5996,24 @@ async function handleSwitchBrowser() {
5897
5996
 
5898
5997
  // src/setup.ts
5899
5998
  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";
5999
+ import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
6000
+ import { homedir as homedir2, platform } from "os";
6001
+ import { dirname as dirname2, resolve as resolve2 } from "path";
5903
6002
  function runSetup(options = {}) {
5904
6003
  const os = platform();
5905
- const binaryPath = getBinaryPath();
5906
6004
  console.log("Viyv Browser MCP - Native Messaging Host Setup");
5907
6005
  console.log("================================================");
5908
6006
  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
6007
  const allowedOrigins = options.extensionId ? [`chrome-extension://${options.extensionId}/`] : ["chrome-extension://*/"];
5915
6008
  if (!options.extensionId) {
5916
6009
  process.stderr.write(
5917
6010
  "WARNING: Using wildcard allowed_origins (chrome-extension://*/). This allows any Chrome extension to connect. For production, specify --extension-id to restrict access.\n"
5918
6011
  );
5919
6012
  }
5920
- const wrapperPath = createNativeHostWrapper(os, binaryPath);
6013
+ syncNativeHostBinary();
6014
+ const nativeHostPath = getNativeHostBinaryPath();
6015
+ console.log(`Native host binary: ${nativeHostPath}`);
6016
+ const wrapperPath = createNativeHostWrapper(os, nativeHostPath);
5921
6017
  const manifest = {
5922
6018
  name: NATIVE_HOST_NAME,
5923
6019
  description: "Viyv Browser MCP Native Messaging Host",
@@ -5929,9 +6025,9 @@ function runSetup(options = {}) {
5929
6025
  const manifestDir = dirname2(manifestPath);
5930
6026
  console.log(`Wrapper: ${wrapperPath}`);
5931
6027
  console.log(`Manifest path: ${manifestPath}`);
5932
- mkdirSync2(manifestDir, { recursive: true });
6028
+ mkdirSync3(manifestDir, { recursive: true });
5933
6029
  writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
5934
- chmodSync(manifestPath, 420);
6030
+ chmodSync2(manifestPath, 420);
5935
6031
  console.log("\nNative Messaging Host registered successfully!");
5936
6032
  console.log("\nNext steps:");
5937
6033
  console.log("1. Start the MCP Server: node <path>/dist/index.js");
@@ -5941,32 +6037,22 @@ function runSetup(options = {}) {
5941
6037
  setupClaudeDesktopConfig();
5942
6038
  }
5943
6039
  }
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
6040
  function createNativeHostWrapper(os, binaryPath) {
5955
6041
  const manifestDir = dirname2(getManifestPath(os));
5956
- mkdirSync2(manifestDir, { recursive: true });
6042
+ mkdirSync3(manifestDir, { recursive: true });
5957
6043
  const nodePath = getNodePath();
5958
6044
  if (os === "win32") {
5959
- const wrapperPath2 = resolve(manifestDir, `${NATIVE_HOST_NAME}.bat`);
6045
+ const wrapperPath2 = resolve2(manifestDir, `${NATIVE_HOST_NAME}.bat`);
5960
6046
  writeFileSync2(wrapperPath2, `@echo off\r
5961
6047
  "${nodePath}" "${binaryPath}" --native-host\r
5962
6048
  `);
5963
6049
  return wrapperPath2;
5964
6050
  }
5965
- const wrapperPath = resolve(manifestDir, `${NATIVE_HOST_NAME}.sh`);
6051
+ const wrapperPath = resolve2(manifestDir, `${NATIVE_HOST_NAME}.sh`);
5966
6052
  writeFileSync2(wrapperPath, `#!/bin/bash
5967
6053
  exec "${nodePath}" "${binaryPath}" --native-host
5968
6054
  `);
5969
- chmodSync(wrapperPath, 493);
6055
+ chmodSync2(wrapperPath, 493);
5970
6056
  return wrapperPath;
5971
6057
  }
5972
6058
  function getNodePath() {
@@ -5977,18 +6063,18 @@ function getNodePath() {
5977
6063
  }
5978
6064
  }
5979
6065
  function getManifestPath(os) {
5980
- const home = homedir();
6066
+ const home = homedir2();
5981
6067
  switch (os) {
5982
6068
  case "darwin":
5983
- return resolve(
6069
+ return resolve2(
5984
6070
  home,
5985
6071
  "Library/Application Support/Google/Chrome/NativeMessagingHosts",
5986
6072
  `${NATIVE_HOST_NAME}.json`
5987
6073
  );
5988
6074
  case "linux":
5989
- return resolve(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
6075
+ return resolve2(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
5990
6076
  case "win32":
5991
- return resolve(
6077
+ return resolve2(
5992
6078
  home,
5993
6079
  "AppData/Local/Google/Chrome/User Data/NativeMessagingHosts",
5994
6080
  `${NATIVE_HOST_NAME}.json`
@@ -6008,7 +6094,7 @@ function setupClaudeDesktopConfig() {
6008
6094
  const configPath = getClaudeDesktopConfigPath();
6009
6095
  console.log(`Config path: ${configPath}`);
6010
6096
  let config = {};
6011
- if (existsSync2(configPath)) {
6097
+ if (existsSync3(configPath)) {
6012
6098
  try {
6013
6099
  config = JSON.parse(readFileSync2(configPath, "utf-8"));
6014
6100
  } catch {
@@ -6030,7 +6116,7 @@ function setupClaudeDesktopConfig() {
6030
6116
  env: { PATH: envPath }
6031
6117
  };
6032
6118
  const configDir = dirname2(configPath);
6033
- mkdirSync2(configDir, { recursive: true });
6119
+ mkdirSync3(configDir, { recursive: true });
6034
6120
  writeFileSync2(configPath, JSON.stringify(config, null, 2));
6035
6121
  console.log(`npx path: ${npxPath}`);
6036
6122
  console.log("\nClaude Desktop config updated successfully!");
@@ -6045,16 +6131,16 @@ function getNpxPath() {
6045
6131
  }
6046
6132
  }
6047
6133
  function getClaudeDesktopConfigPath() {
6048
- const home = homedir();
6134
+ const home = homedir2();
6049
6135
  switch (process.platform) {
6050
6136
  case "darwin":
6051
- return resolve(home, "Library/Application Support/Claude/claude_desktop_config.json");
6137
+ return resolve2(home, "Library/Application Support/Claude/claude_desktop_config.json");
6052
6138
  case "linux":
6053
- return resolve(home, ".config/Claude/claude_desktop_config.json");
6139
+ return resolve2(home, ".config/Claude/claude_desktop_config.json");
6054
6140
  case "win32":
6055
- return resolve(home, "AppData/Roaming/Claude/claude_desktop_config.json");
6141
+ return resolve2(home, "AppData/Roaming/Claude/claude_desktop_config.json");
6056
6142
  default:
6057
- return resolve(home, ".config/Claude/claude_desktop_config.json");
6143
+ return resolve2(home, ".config/Claude/claude_desktop_config.json");
6058
6144
  }
6059
6145
  }
6060
6146
 
@@ -6089,6 +6175,16 @@ if (args.includes("setup")) {
6089
6175
  const port = portIdx >= 0 ? Number(args[portIdx + 1]) : void 0;
6090
6176
  const chromeProfileIdx = args.indexOf("--chrome-profile");
6091
6177
  const chromeProfile = chromeProfileIdx >= 0 ? args[chromeProfileIdx + 1] : process.env.VIYV_CHROME_PROFILE;
6178
+ if (isNativeHostDeployed()) {
6179
+ try {
6180
+ syncNativeHostBinary();
6181
+ } catch (err) {
6182
+ process.stderr.write(
6183
+ `[viyv-browser:mcp] WARNING: Failed to update native host binary: ${err.message}
6184
+ `
6185
+ );
6186
+ }
6187
+ }
6092
6188
  startMcpServer(agentName, { transport: transportMode, port, chromeProfile });
6093
6189
  }
6094
6190
  //# sourceMappingURL=index.js.map