@triflux/remote 10.27.0 → 10.28.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/hub/team/claude-daemon-control.mjs +214 -0
- package/hub/team/claude-native-bridge.mjs +45 -73
- package/hub/team/daemon-pty-tmux-bridge.mjs +146 -0
- package/hub/team/headless.mjs +23 -42
- package/hub/team/interactive-native-launcher.mjs +88 -75
- package/hub/team/retry-state-machine.mjs +2 -2
- package/hub/workers/codex-app-server-worker.mjs +4 -1
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +686 -0
- package/package.json +1 -1
- package/scripts/lib/mcp-gateway-health-check.mjs +65 -0
- package/scripts/lib/mcp-guard-engine.mjs +52 -8
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// 침묵한다 (false-positive 방지).
|
|
15
15
|
|
|
16
16
|
import { readFileSync, statSync } from "node:fs";
|
|
17
|
+
import { createConnection } from "node:net";
|
|
17
18
|
import { homedir } from "node:os";
|
|
18
19
|
import { join } from "node:path";
|
|
19
20
|
|
|
@@ -33,6 +34,27 @@ const START_RE = /^\[START\]\s+(\S+)\s+on\s+:(\d+)$/;
|
|
|
33
34
|
const SKIP_RUNNING_RE = /^\[SKIP\]\s+(\S+)\s+already running on\s+:(\d+)$/;
|
|
34
35
|
const SKIP_DISABLED_RE = /^\[SKIP\]\s+(\S+)\s+—\s+manifest에서 비활성$/;
|
|
35
36
|
|
|
37
|
+
function probePortListening(
|
|
38
|
+
port,
|
|
39
|
+
{ host = "127.0.0.1", timeoutMs = 800 } = {},
|
|
40
|
+
) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const socket = createConnection({ host, port });
|
|
43
|
+
let settled = false;
|
|
44
|
+
|
|
45
|
+
function finish(value) {
|
|
46
|
+
if (settled) return;
|
|
47
|
+
settled = true;
|
|
48
|
+
socket.destroy();
|
|
49
|
+
resolve(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
socket.once("connect", () => finish(true));
|
|
53
|
+
socket.once("error", () => finish(false));
|
|
54
|
+
socket.setTimeout(timeoutMs, () => finish(false));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
export function checkMcpGatewayHealth({
|
|
37
59
|
fs = { readFileSync, statSync },
|
|
38
60
|
logPath = DEFAULT_LOG_PATH,
|
|
@@ -130,6 +152,40 @@ export function checkMcpGatewayHealth({
|
|
|
130
152
|
};
|
|
131
153
|
}
|
|
132
154
|
|
|
155
|
+
export async function checkMcpGatewayHealthLive({
|
|
156
|
+
probePort = probePortListening,
|
|
157
|
+
...options
|
|
158
|
+
} = {}) {
|
|
159
|
+
const result = checkMcpGatewayHealth(options);
|
|
160
|
+
if (!result.available || result.started.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
...result,
|
|
163
|
+
live: [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const live = [];
|
|
168
|
+
const findings = [...result.findings];
|
|
169
|
+
for (const entry of result.started) {
|
|
170
|
+
const listening = await probePort(entry.port);
|
|
171
|
+
live.push({ ...entry, listening });
|
|
172
|
+
if (!listening) {
|
|
173
|
+
findings.push({
|
|
174
|
+
server: entry.server,
|
|
175
|
+
reason: "port-down",
|
|
176
|
+
detail: `expected listener on :${entry.port}`,
|
|
177
|
+
port: entry.port,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
...result,
|
|
184
|
+
findings,
|
|
185
|
+
live,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
133
189
|
export function summarizeMcpGatewayHealth(result) {
|
|
134
190
|
if (!result?.available) {
|
|
135
191
|
return {
|
|
@@ -152,6 +208,15 @@ export function summarizeMcpGatewayHealth(result) {
|
|
|
152
208
|
fix: "secrets 를 ~/.config/triflux/secrets.env 에 추가하고 `launchctl kickstart -k gui/$(id -u)/com.tellang.mcp-gateway` (macOS) 또는 `systemctl --user restart mcp-gateway` (linux)",
|
|
153
209
|
};
|
|
154
210
|
}
|
|
211
|
+
const portDown = result.findings.filter((f) => f.reason === "port-down");
|
|
212
|
+
if (portDown.length > 0) {
|
|
213
|
+
const list = portDown.map((f) => `${f.server} (:${f.port})`).join(", ");
|
|
214
|
+
return {
|
|
215
|
+
level: "warn",
|
|
216
|
+
message: `${portDown.length}개 MCP gateway port down: ${list}`,
|
|
217
|
+
fix: "gateway 를 재기동하세요: `node scripts/mcp-gateway-start.mjs` 또는 macOS `launchctl kickstart -k gui/$(id -u)/com.tellang.mcp-gateway`",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
155
220
|
return {
|
|
156
221
|
level: "warn",
|
|
157
222
|
message: `${result.findings.length}개 gateway 이슈`,
|
|
@@ -22,7 +22,12 @@ const PROJECT_ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
|
22
22
|
const DEFAULT_REGISTRY_PATH = join(PROJECT_ROOT, "config", "mcp-registry.json");
|
|
23
23
|
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
24
24
|
const DEFAULT_HUB_PATH = "/mcp";
|
|
25
|
-
const SERVER_POLICIES = new Set([
|
|
25
|
+
const SERVER_POLICIES = new Set([
|
|
26
|
+
"hosted",
|
|
27
|
+
"gateway-sse",
|
|
28
|
+
"gateway-http",
|
|
29
|
+
"stdio",
|
|
30
|
+
]);
|
|
26
31
|
const GATEWAY_PORTS = Object.freeze({
|
|
27
32
|
context7: 8100,
|
|
28
33
|
"brave-search": 8101,
|
|
@@ -792,6 +797,23 @@ function gatewaySseUrl(name, serverConfig = {}) {
|
|
|
792
797
|
return `http://127.0.0.1:${port}/sse`;
|
|
793
798
|
}
|
|
794
799
|
|
|
800
|
+
function gatewayHttpUrl(name, serverConfig = {}) {
|
|
801
|
+
if (typeof serverConfig.url === "string" && serverConfig.url.trim()) {
|
|
802
|
+
return normalizeUrl(serverConfig.url);
|
|
803
|
+
}
|
|
804
|
+
const port =
|
|
805
|
+
Number(serverConfig.gateway_port || serverConfig.port || 0) ||
|
|
806
|
+
GATEWAY_PORTS[name];
|
|
807
|
+
if (!Number.isFinite(port) || port <= 0) return "";
|
|
808
|
+
const path =
|
|
809
|
+
typeof serverConfig.gateway_path === "string" &&
|
|
810
|
+
serverConfig.gateway_path.trim()
|
|
811
|
+
? serverConfig.gateway_path.trim()
|
|
812
|
+
: "/mcp";
|
|
813
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
814
|
+
return `http://127.0.0.1:${port}${normalizedPath}`;
|
|
815
|
+
}
|
|
816
|
+
|
|
795
817
|
function syncDenylistEntries(policy = {}) {
|
|
796
818
|
if (!Array.isArray(policy?.sync_denylist)) return new Set();
|
|
797
819
|
return new Set(
|
|
@@ -836,9 +858,11 @@ export function buildDesiredServerRecord(name, serverConfig, filePath) {
|
|
|
836
858
|
const url =
|
|
837
859
|
policy === "gateway-sse"
|
|
838
860
|
? gatewaySseUrl(name, serverConfig)
|
|
839
|
-
:
|
|
840
|
-
?
|
|
841
|
-
:
|
|
861
|
+
: policy === "gateway-http"
|
|
862
|
+
? gatewayHttpUrl(name, serverConfig)
|
|
863
|
+
: serverConfig?.transport === "hub-url"
|
|
864
|
+
? resolveHubUrl()
|
|
865
|
+
: normalizeUrl(serverConfig?.url || "");
|
|
842
866
|
const basenameValue = pathBasename(filePath);
|
|
843
867
|
const resolvedHeaders = resolveHeaderDescriptors(
|
|
844
868
|
name,
|
|
@@ -849,6 +873,26 @@ export function buildDesiredServerRecord(name, serverConfig, filePath) {
|
|
|
849
873
|
? { headers: resolvedHeaders.headers }
|
|
850
874
|
: {};
|
|
851
875
|
|
|
876
|
+
if (policy === "gateway-http") {
|
|
877
|
+
if (isCodexConfig(filePath)) {
|
|
878
|
+
return {
|
|
879
|
+
name,
|
|
880
|
+
config: { url },
|
|
881
|
+
headerDescriptors: {},
|
|
882
|
+
codex: {},
|
|
883
|
+
warnings: [],
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
name,
|
|
889
|
+
config: { type: "http", url },
|
|
890
|
+
headerDescriptors: {},
|
|
891
|
+
codex: {},
|
|
892
|
+
warnings: [],
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
852
896
|
if (policy === "gateway-sse") {
|
|
853
897
|
if (isCodexConfig(filePath)) {
|
|
854
898
|
return {
|
|
@@ -1237,7 +1281,7 @@ export function validateRegistry(registry) {
|
|
|
1237
1281
|
!SERVER_POLICIES.has(server.policy)
|
|
1238
1282
|
) {
|
|
1239
1283
|
errors.push(
|
|
1240
|
-
`registry.servers.${name}.policy must be hosted, gateway-sse, or stdio`,
|
|
1284
|
+
`registry.servers.${name}.policy must be hosted, gateway-sse, gateway-http, or stdio`,
|
|
1241
1285
|
);
|
|
1242
1286
|
}
|
|
1243
1287
|
const transport = server.transport || registry.defaults?.transport;
|
|
@@ -1252,7 +1296,7 @@ export function validateRegistry(registry) {
|
|
|
1252
1296
|
) {
|
|
1253
1297
|
errors.push(`registry.servers.${name}.url must be a non-empty string`);
|
|
1254
1298
|
}
|
|
1255
|
-
if (policy === "gateway-sse") {
|
|
1299
|
+
if (policy === "gateway-sse" || policy === "gateway-http") {
|
|
1256
1300
|
const port = Number(server.gateway_port || server.port || 0);
|
|
1257
1301
|
const hasGatewayUrl =
|
|
1258
1302
|
typeof server.url === "string" && server.url.trim().length > 0;
|
|
@@ -1261,7 +1305,7 @@ export function validateRegistry(registry) {
|
|
|
1261
1305
|
(!Number.isInteger(port) || port <= 0 || port > 65535)
|
|
1262
1306
|
) {
|
|
1263
1307
|
errors.push(
|
|
1264
|
-
`registry.servers.${name}.gateway_port must be a valid TCP port for
|
|
1308
|
+
`registry.servers.${name}.gateway_port must be a valid TCP port for ${policy}`,
|
|
1265
1309
|
);
|
|
1266
1310
|
}
|
|
1267
1311
|
}
|
|
@@ -1913,7 +1957,7 @@ export function addRegistryServer(name, url, options = {}) {
|
|
|
1913
1957
|
(transport === "stdio"
|
|
1914
1958
|
? "stdio"
|
|
1915
1959
|
: options.gateway_port || options.port
|
|
1916
|
-
? "gateway-
|
|
1960
|
+
? "gateway-http"
|
|
1917
1961
|
: "hosted");
|
|
1918
1962
|
|
|
1919
1963
|
registry.servers[trimmedName] = {
|