clay-server 4.9.0-beta.1 → 5.0.0-beta.1

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.
@@ -3,6 +3,7 @@ var readline = require("readline");
3
3
  var spawn = require("child_process").spawn;
4
4
  var chromium = require("playwright").chromium;
5
5
  var browser = null;
6
+ var loginProxy = null;
6
7
  var page = null;
7
8
  var installing = null;
8
9
  var viewing = true;
@@ -22,7 +23,7 @@ async function launch() {
22
23
  var channels = ["chrome", "msedge", null];
23
24
  for (var i = 0; i < channels.length; i++) {
24
25
  try {
25
- return await chromium.launch({ channel: channels[i] || undefined, headless: true, chromiumSandbox: true, timeout: 20000 });
26
+ return await chromium.launch({ channel: channels[i] || undefined, headless: true, chromiumSandbox: true, timeout: 20000, proxy: loginProxy ? { server: loginProxy.server } : undefined, args: loginProxy ? ["--force-webrtc-ip-handling-policy=disable_non_proxied_udp"] : [] });
26
27
  } catch (e) {
27
28
  if (closing) throw e;
28
29
  }
@@ -39,7 +40,7 @@ async function launch() {
39
40
  });
40
41
  });
41
42
  if (closing) throw new Error("Closed");
42
- return chromium.launch({ headless: true, chromiumSandbox: true, timeout: 20000 });
43
+ return chromium.launch({ headless: true, chromiumSandbox: true, timeout: 20000, proxy: loginProxy ? { server: loginProxy.server } : undefined, args: loginProxy ? ["--force-webrtc-ip-handling-policy=disable_non_proxied_udp"] : [] });
43
44
  }
44
45
 
45
46
  async function capture() {
@@ -55,7 +56,8 @@ async function capture() {
55
56
  if (!closing) captureTimer = setTimeout(capture, 250);
56
57
  }
57
58
 
58
- async function start(url) {
59
+ async function start(url, restrictedNetwork) {
60
+ if (restrictedNetwork) loginProxy = await require("./mcp-browser-proxy").createBrowserProxy();
59
61
  browser = await launch();
60
62
  if (closing) { await browser.close(); return; }
61
63
  var session = await browser.newBrowserCDPSession();
@@ -64,12 +66,19 @@ async function start(url) {
64
66
  finally { await session.detach(); }
65
67
  // Preserve the native version/platform and replace only the headless product token.
66
68
  var userAgent = version.userAgent.replace("HeadlessChrome/", "Chrome/");
67
- var context = await browser.newContext({ viewport: viewport, acceptDownloads: false, userAgent: userAgent });
69
+ var context = await browser.newContext({ viewport: viewport, acceptDownloads: false, serviceWorkers: "block", userAgent: userAgent });
70
+ if (restrictedNetwork) await context.routeWebSocket("**/*", function (socket) { socket.close(); });
68
71
  await context.addInitScript(require("./vendor-login-browser-passkeys").disableLoginPasskeys);
69
72
  var callback = new URL(new URL(url).searchParams.get("redirect_uri"));
70
- await context.route("**/*", function (route) {
73
+ await context.route("**/*", async function (route) {
71
74
  var target = new URL(route.request().url());
72
- if (target.protocol === "https:" || (target.origin === callback.origin && target.pathname === callback.pathname)) return route.continue();
75
+ if (target.origin === callback.origin && target.pathname === callback.pathname) return route.continue();
76
+ if (target.protocol === "https:") {
77
+ if (restrictedNetwork) {
78
+ try { await require("./mcp-network").assertPublicUrl(target.href); } catch (error) { return route.abort(); }
79
+ }
80
+ return route.continue();
81
+ }
73
82
  return route.abort();
74
83
  });
75
84
  context.on("page", function (opened) {
@@ -120,6 +129,7 @@ async function close() {
120
129
  clearTimeout(captureTimer);
121
130
  if (installing) installing.kill();
122
131
  if (browser) await browser.close().catch(function () {});
132
+ if (loginProxy) loginProxy.close();
123
133
  process.exit(0);
124
134
  }
125
135
 
@@ -131,8 +141,8 @@ lines.on("line", function (line) {
131
141
  try { message = JSON.parse(line); } catch (e) { return; }
132
142
  if (message.type === "start" && !started) {
133
143
  started = true;
134
- start(message.url).catch(function () {
135
- status("Unable to open the sign-in browser. The host may need browser libraries or sandbox support. Use terminal sign-in to continue.", "error");
144
+ start(message.url, message.restrictedNetwork).catch(function () {
145
+ status("Unable to open the sign-in browser. The host may need browser libraries or sandbox support." + (message.restrictedNetwork ? " Retry or use an access token." : " Use terminal sign-in to continue."), "error");
136
146
  });
137
147
  } else if (message.type === "viewing") {
138
148
  viewing = !!message.value;
@@ -20,6 +20,12 @@ function loginUrl(value, vendor) {
20
20
  }
21
21
 
22
22
  function createLoginBrowser(options) {
23
+ var initial = null;
24
+ if (options.initialUrl) {
25
+ initial = new URL(options.initialUrl);
26
+ var callback = new URL(initial.searchParams.get("redirect_uri"));
27
+ if (options.vendor !== "mcp" || !options.restrictedNetwork || initial.protocol !== "https:" || initial.username || initial.password || !initial.searchParams.get("state") || !initial.searchParams.get("code_challenge") || callback.protocol !== "http:" || callback.hostname !== "127.0.0.1" || callback.pathname !== "/callback") throw new Error("Invalid MCP sign-in URL");
28
+ }
23
29
  var id = crypto.randomUUID();
24
30
  var dir = fs.mkdtempSync(path.join(os.tmpdir(), "clay-login-"));
25
31
  fs.chmodSync(dir, 0o700);
@@ -98,17 +104,17 @@ function createLoginBrowser(options) {
98
104
  });
99
105
  child.on("error", function () {
100
106
  statusPhase = "error";
101
- status = "The sign-in browser could not start. Use terminal sign-in to continue.";
107
+ status = "The sign-in browser could not start." + (options.vendor === "mcp" ? " Retry or use an access token." : " Use terminal sign-in to continue.");
102
108
  send({ type: "vendor_login_browser_status", text: status, phase: statusPhase });
103
109
  });
104
110
  child.on("exit", function () {
105
111
  if (closed) return;
106
112
  statusPhase = "error";
107
- status = "The sign-in browser closed. You can retry or use terminal sign-in.";
113
+ status = "The sign-in browser closed. You can retry.";
108
114
  send({ type: "vendor_login_browser_status", text: status, phase: statusPhase });
109
115
  });
110
116
  child.stdin.on("error", function () {});
111
- write({ type: "start", url: url });
117
+ write({ type: "start", url: url, restrictedNetwork: !!options.restrictedNetwork });
112
118
  }
113
119
  function acceptUrl(value) {
114
120
  var url = loginUrl(value, options.vendor);
@@ -123,6 +129,7 @@ function createLoginBrowser(options) {
123
129
  });
124
130
  }, 250);
125
131
  timer.unref();
132
+ if (initial) start(initial.href);
126
133
  return {
127
134
  id: id,
128
135
  env: { BROWSER: wrapper, CLAY_LOGIN_URL_FILE: urlFile },
@@ -1853,25 +1853,8 @@ function createCodexAdapter(opts) {
1853
1853
  if (co.config) serverOpts.config = Object.assign(serverOpts.config, co.config);
1854
1854
  }
1855
1855
 
1856
- // Track 1: Read local MCP server definitions from ~/.clay/mcp.json
1857
- // and inject into Codex config so Codex manages them natively.
1856
+ // Clay-managed external MCP runs through the owner-bound bridge.
1858
1857
  var mcpServerConfig = Object.assign({}, serverOpts.config.mcp_servers || {});
1859
- try {
1860
- var mcpLocal = require("../../mcp-local");
1861
- var localMcpServers = mcpLocal.readMergedServers();
1862
- var mcpNames = Object.keys(localMcpServers);
1863
- for (var mi = 0; mi < mcpNames.length; mi++) {
1864
- var ms = localMcpServers[mcpNames[mi]];
1865
- if (ms.command) {
1866
- mcpServerConfig[mcpNames[mi]] = { command: ms.command, args: ms.args || [] };
1867
- if (ms.env && Object.keys(ms.env).length > 0) {
1868
- mcpServerConfig[mcpNames[mi]].env = ms.env;
1869
- }
1870
- }
1871
- }
1872
- } catch (e) {
1873
- console.error("[codex] Failed to read local MCP config:", e.message);
1874
- }
1875
1858
 
1876
1859
  // Track 2: Add clay-tools bridge server for in-app + remote MCP tools.
1877
1860
  var bridgePath = require("path").join(__dirname, "..", "mcp-bridge-server.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.9.0-beta.1",
3
+ "version": "5.0.0-beta.1",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js"
@@ -49,14 +49,18 @@
49
49
  "dependencies": {
50
50
  "@anthropic-ai/claude-agent-sdk": "^0.3.287",
51
51
  "@lydell/node-pty": "^1.2.0-beta.3",
52
+ "@modelcontextprotocol/sdk": "^1.32.1",
52
53
  "@openai/codex": "^0.160.0",
53
54
  "@seald-io/nedb": "^4.1.2",
54
55
  "imapflow": "^1.7.8",
56
+ "ipaddr.js": "^2.5.0",
55
57
  "nodemailer": "^9.1.1",
56
58
  "playwright": "1.63.0",
57
59
  "qrcode-terminal": "^0.12.0",
60
+ "undici": "^6.13.0",
58
61
  "web-push": "^3.6.7",
59
- "ws": "^8.18.0"
62
+ "ws": "^8.18.0",
63
+ "zod": "^4.6.5"
60
64
  },
61
65
  "license": "AGPL-3.0-only",
62
66
  "engines": {