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.
- package/lib/mcp-browser-proxy.js +37 -0
- package/lib/mcp-connection-store.js +39 -0
- package/lib/mcp-network.js +46 -0
- package/lib/mcp-oauth.js +65 -0
- package/lib/mcp-proxy.js +32 -0
- package/lib/mcp-remote-connections.js +215 -0
- package/lib/mcp-session-access.js +15 -0
- package/lib/project-mcp.js +130 -357
- package/lib/project-user-message.js +2 -0
- package/lib/project.js +28 -45
- package/lib/public/app.js +1 -0
- package/lib/public/css/mcp-connections.css +86 -0
- package/lib/public/css/skills-workbench.css +74 -0
- package/lib/public/index.html +16 -15
- package/lib/public/modules/app-messages.js +2 -1
- package/lib/public/modules/mcp-connection-view.js +136 -0
- package/lib/public/modules/mcp-permissions.js +122 -0
- package/lib/public/modules/mcp-ui.js +85 -461
- package/lib/public/modules/skills-actions.js +47 -79
- package/lib/public/modules/skills-detail.js +32 -145
- package/lib/public/modules/skills-list-view.js +29 -0
- package/lib/public/modules/skills-state.js +4 -2
- package/lib/public/modules/skills.js +104 -399
- package/lib/public/modules/vendor-login-browser.js +16 -5
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +3 -16
- package/lib/sdk-mcp-descriptors.js +2 -1
- package/lib/vendor-login-browser-worker.js +18 -8
- package/lib/vendor-login-browser.js +10 -3
- package/lib/yoke/adapters/codex.js +1 -18
- package/package.json +6 -2
|
@@ -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.
|
|
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
|
|
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
|
-
//
|
|
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": "
|
|
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": {
|