@triflux/remote 10.49.0 → 10.50.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.
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  chmodSync,
3
- copyFileSync,
4
3
  existsSync,
5
4
  mkdirSync,
6
5
  readdirSync,
@@ -22,22 +21,7 @@ const PROJECT_ROOT = fileURLToPath(new URL("../../", import.meta.url));
22
21
  const DEFAULT_REGISTRY_PATH = join(PROJECT_ROOT, "config", "mcp-registry.json");
23
22
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
24
23
  const DEFAULT_HUB_PATH = "/mcp";
25
- const SERVER_POLICIES = new Set([
26
- "hosted",
27
- "gateway-sse",
28
- "gateway-http",
29
- "stdio",
30
- ]);
31
- const GATEWAY_PORTS = Object.freeze({
32
- context7: 8100,
33
- "brave-search": 8101,
34
- exa: 8102,
35
- tavily: 8103,
36
- jira: 8104,
37
- serena: 8105,
38
- notion: 8106,
39
- "notion-guest": 8107,
40
- });
24
+ const SERVER_POLICIES = new Set(["hosted", "stdio"]);
41
25
  const DEFAULT_REGISTRY = Object.freeze({
42
26
  $schema: "mcp-registry-schema",
43
27
  version: 1,
@@ -65,7 +49,6 @@ const DEFAULT_REGISTRY = Object.freeze({
65
49
  },
66
50
  },
67
51
  policies: {
68
- stdio_action: "replace-with-hub",
69
52
  unknown_server_action: "warn",
70
53
  sync_denylist: [],
71
54
  watched_paths: [
@@ -153,12 +136,6 @@ function registryPath() {
153
136
  : DEFAULT_REGISTRY_PATH;
154
137
  }
155
138
 
156
- function ensureBackup(filePath) {
157
- const backupPath = `${filePath}.bak`;
158
- copyFileSync(filePath, backupPath);
159
- return backupPath;
160
- }
161
-
162
139
  function isJsonMcpConfig(filePath) {
163
140
  const name = pathBasename(filePath);
164
141
  return (
@@ -484,12 +461,9 @@ function resolveEnvDescriptors(name, serverConfig) {
484
461
  const envName = descriptor.env;
485
462
  const envValue = process.env[envName];
486
463
  if (typeof envValue !== "string" || envValue.length === 0) {
487
- warnings.push(
488
- `[mcp-guard] ${name}.${envKey} env skipped: env ${envName} is not set`,
489
- );
490
- continue;
464
+ warnings.push(`[mcp-guard] ${name}.${envKey} env ${envName} is not set`);
491
465
  }
492
- env[envKey] = envValue;
466
+ env[envKey] = "${" + envName + "}";
493
467
  }
494
468
 
495
469
  return { descriptors, env, warnings };
@@ -567,22 +541,6 @@ function hasOwnEntries(value) {
567
541
  return value && typeof value === "object" && Object.keys(value).length > 0;
568
542
  }
569
543
 
570
- function redactHeaders(headers = {}) {
571
- const redacted = {};
572
- for (const key of Object.keys(headers || {})) {
573
- redacted[key] = "***REDACTED***";
574
- }
575
- return redacted;
576
- }
577
-
578
- function redactServerConfig(config = {}) {
579
- if (!config || typeof config !== "object") return config;
580
- return {
581
- ...config,
582
- ...(config.headers ? { headers: redactHeaders(config.headers) } : {}),
583
- };
584
- }
585
-
586
544
  function descriptorsFromCodexConfig(config = {}) {
587
545
  const descriptors = {};
588
546
  if (typeof config.bearer_token_env_var === "string") {
@@ -691,6 +649,7 @@ function upsertTomlServer(raw, name, config, codex = {}) {
691
649
  "http_headers",
692
650
  "env_http_headers",
693
651
  "env",
652
+ "env_vars",
694
653
  ]);
695
654
  const nextManagedLines = [];
696
655
  if (typeof config.url === "string") {
@@ -707,6 +666,9 @@ function upsertTomlServer(raw, name, config, codex = {}) {
707
666
  }
708
667
  const env = formatTomlInlineTable(config.env || {});
709
668
  if (env) nextManagedLines.push(`env = ${env}`);
669
+ if (Array.isArray(codex.env_vars) && codex.env_vars.length > 0) {
670
+ nextManagedLines.push(`env_vars = ${formatTomlArray(codex.env_vars)}`);
671
+ }
710
672
  if (codex.bearer_token_env_var) {
711
673
  nextManagedLines.push(
712
674
  `bearer_token_env_var = ${formatTomlString(codex.bearer_token_env_var)}`,
@@ -795,34 +757,6 @@ function serverPolicy(serverConfig = {}, registry = {}) {
795
757
  return "hosted";
796
758
  }
797
759
 
798
- function gatewaySseUrl(name, serverConfig = {}) {
799
- if (typeof serverConfig.url === "string" && serverConfig.url.trim()) {
800
- return normalizeUrl(serverConfig.url);
801
- }
802
- const port =
803
- Number(serverConfig.gateway_port || serverConfig.port || 0) ||
804
- GATEWAY_PORTS[name];
805
- if (!Number.isFinite(port) || port <= 0) return "";
806
- return `http://127.0.0.1:${port}/sse`;
807
- }
808
-
809
- function gatewayHttpUrl(name, serverConfig = {}) {
810
- if (typeof serverConfig.url === "string" && serverConfig.url.trim()) {
811
- return normalizeUrl(serverConfig.url);
812
- }
813
- const port =
814
- Number(serverConfig.gateway_port || serverConfig.port || 0) ||
815
- GATEWAY_PORTS[name];
816
- if (!Number.isFinite(port) || port <= 0) return "";
817
- const path =
818
- typeof serverConfig.gateway_path === "string" &&
819
- serverConfig.gateway_path.trim()
820
- ? serverConfig.gateway_path.trim()
821
- : "/mcp";
822
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
823
- return `http://127.0.0.1:${port}${normalizedPath}`;
824
- }
825
-
826
760
  function syncDenylistEntries(policy = {}) {
827
761
  if (!Array.isArray(policy?.sync_denylist)) return new Set();
828
762
  return new Set(
@@ -848,9 +782,11 @@ export function buildDesiredServerRecord(name, serverConfig, filePath) {
848
782
  const policy = serverPolicy(serverConfig);
849
783
  if (policy === "stdio") {
850
784
  const resolvedEnv = resolveEnvDescriptors(name, serverConfig);
851
- const envConfig = hasOwnEntries(resolvedEnv.env)
852
- ? { env: resolvedEnv.env }
853
- : {};
785
+ const codexTarget = isCodexConfig(filePath);
786
+ const envConfig =
787
+ !codexTarget && hasOwnEntries(resolvedEnv.env)
788
+ ? { env: resolvedEnv.env }
789
+ : {};
854
790
  return {
855
791
  name,
856
792
  config: {
@@ -859,19 +795,21 @@ export function buildDesiredServerRecord(name, serverConfig, filePath) {
859
795
  ...envConfig,
860
796
  },
861
797
  envDescriptors: resolvedEnv.descriptors,
862
- codex: {},
798
+ codex: codexTarget
799
+ ? {
800
+ env_vars: Object.values(resolvedEnv.descriptors).map(
801
+ (item) => item.env,
802
+ ),
803
+ }
804
+ : {},
863
805
  warnings: resolvedEnv.warnings,
864
806
  };
865
807
  }
866
808
 
867
809
  const url =
868
- policy === "gateway-sse"
869
- ? gatewaySseUrl(name, serverConfig)
870
- : policy === "gateway-http"
871
- ? gatewayHttpUrl(name, serverConfig)
872
- : serverConfig?.transport === "hub-url"
873
- ? resolveHubUrl()
874
- : normalizeUrl(serverConfig?.url || "");
810
+ serverConfig?.transport === "hub-url"
811
+ ? resolveHubUrl()
812
+ : normalizeUrl(serverConfig?.url || "");
875
813
  const basenameValue = pathBasename(filePath);
876
814
  const resolvedHeaders = resolveHeaderDescriptors(
877
815
  name,
@@ -882,46 +820,6 @@ export function buildDesiredServerRecord(name, serverConfig, filePath) {
882
820
  ? { headers: resolvedHeaders.headers }
883
821
  : {};
884
822
 
885
- if (policy === "gateway-http") {
886
- if (isCodexConfig(filePath)) {
887
- return {
888
- name,
889
- config: { url },
890
- headerDescriptors: {},
891
- codex: {},
892
- warnings: [],
893
- };
894
- }
895
-
896
- return {
897
- name,
898
- config: { type: "http", url },
899
- headerDescriptors: {},
900
- codex: {},
901
- warnings: [],
902
- };
903
- }
904
-
905
- if (policy === "gateway-sse") {
906
- if (isCodexConfig(filePath)) {
907
- return {
908
- name,
909
- config: { url, transport: "sse" },
910
- headerDescriptors: {},
911
- codex: {},
912
- warnings: [],
913
- };
914
- }
915
-
916
- return {
917
- name,
918
- config: { type: "sse", url },
919
- headerDescriptors: {},
920
- codex: {},
921
- warnings: [],
922
- };
923
- }
924
-
925
823
  if (
926
824
  basenameValue === ".mcp.json" ||
927
825
  isClaudeProjectMcpConfig(filePath) ||
@@ -1122,7 +1020,6 @@ function scanCodexConfig(filePath) {
1122
1020
  exists: true,
1123
1021
  parseError: null,
1124
1022
  servers,
1125
- // Wave 1/2-B: Codex stdio servers are observed but not auto-remediated.
1126
1023
  stdioServers: [],
1127
1024
  };
1128
1025
  } catch (error) {
@@ -1305,9 +1202,7 @@ export function validateRegistry(registry) {
1305
1202
  typeof server.policy !== "string" ||
1306
1203
  !SERVER_POLICIES.has(server.policy)
1307
1204
  ) {
1308
- errors.push(
1309
- `registry.servers.${name}.policy must be hosted, gateway-sse, gateway-http, or stdio`,
1310
- );
1205
+ errors.push(`registry.servers.${name}.policy must be hosted or stdio`);
1311
1206
  }
1312
1207
  const transport = server.transport || registry.defaults?.transport;
1313
1208
  if (!["hub-url", "http", "stdio"].includes(transport)) {
@@ -1321,19 +1216,6 @@ export function validateRegistry(registry) {
1321
1216
  ) {
1322
1217
  errors.push(`registry.servers.${name}.url must be a non-empty string`);
1323
1218
  }
1324
- if (policy === "gateway-sse" || policy === "gateway-http") {
1325
- const port = Number(server.gateway_port || server.port || 0);
1326
- const hasGatewayUrl =
1327
- typeof server.url === "string" && server.url.trim().length > 0;
1328
- if (
1329
- !hasGatewayUrl &&
1330
- (!Number.isInteger(port) || port <= 0 || port > 65535)
1331
- ) {
1332
- errors.push(
1333
- `registry.servers.${name}.gateway_port must be a valid TCP port for ${policy}`,
1334
- );
1335
- }
1336
- }
1337
1219
  if (policy === "stdio" && server.url !== undefined) {
1338
1220
  errors.push(`registry.servers.${name}.url is not used for stdio`);
1339
1221
  }
@@ -1520,14 +1402,6 @@ export function validateRegistry(registry) {
1520
1402
  }
1521
1403
  }
1522
1404
  }
1523
- if (
1524
- registry.policies.stdio_action &&
1525
- !["replace-with-hub", "warn"].includes(registry.policies.stdio_action)
1526
- ) {
1527
- errors.push(
1528
- "registry.policies.stdio_action must be replace-with-hub or warn",
1529
- );
1530
- }
1531
1405
  }
1532
1406
 
1533
1407
  return errors;
@@ -1795,101 +1669,6 @@ export function scanForStdioServers(filePath) {
1795
1669
  return scanConfig(filePath).stdioServers;
1796
1670
  }
1797
1671
 
1798
- export function remediate(filePath, stdioServers, policy = {}) {
1799
- const resolvedPath = resolveFilePath(filePath);
1800
- const offenders = Array.isArray(stdioServers)
1801
- ? stdioServers.filter((server) => server?.name)
1802
- : [];
1803
- const action = policy?.stdio_action || "warn";
1804
-
1805
- if (offenders.length === 0) {
1806
- return {
1807
- action: "noop",
1808
- modified: false,
1809
- backupPath: null,
1810
- removedServers: [],
1811
- warnings: [],
1812
- };
1813
- }
1814
-
1815
- if (action === "warn") {
1816
- return {
1817
- action,
1818
- modified: false,
1819
- backupPath: null,
1820
- removedServers: [],
1821
- warnings: [
1822
- `[mcp-guard] stdio MCP 감지: ${offenders.map((server) => server.name).join(", ")}`,
1823
- ],
1824
- };
1825
- }
1826
-
1827
- if (isCodexConfig(resolvedPath)) {
1828
- return {
1829
- action,
1830
- modified: false,
1831
- backupPath: null,
1832
- removedServers: [],
1833
- warnings: ["[mcp-guard] Codex TOML 자동 수정은 Wave 2-B 범위 밖입니다."],
1834
- };
1835
- }
1836
-
1837
- const snapshot = scanConfig(resolvedPath);
1838
- if (snapshot.parseError) {
1839
- return {
1840
- action,
1841
- modified: false,
1842
- backupPath: null,
1843
- removedServers: [],
1844
- warnings: [`[mcp-guard] 설정 파싱 실패: ${snapshot.parseError.message}`],
1845
- };
1846
- }
1847
-
1848
- let backupPath = null;
1849
- try {
1850
- if (existsSync(resolvedPath)) backupPath = ensureBackup(resolvedPath);
1851
- } catch (error) {
1852
- return {
1853
- action,
1854
- modified: false,
1855
- backupPath: null,
1856
- removedServers: [],
1857
- warnings: [`[mcp-guard] 백업 생성 실패: ${error.message}`],
1858
- };
1859
- }
1860
-
1861
- const removals = offenders.map((server) => server.name);
1862
- const updates = [];
1863
- let replacement = null;
1864
-
1865
- if (action === "replace-with-hub") {
1866
- const registry = policy.registry || loadRegistryOrDefault();
1867
- const matchingOffender = offenders.find((server) =>
1868
- Object.hasOwn(registry.servers || {}, server.name),
1869
- );
1870
- const [hubServerName, hubServerConfig] = matchingOffender
1871
- ? [matchingOffender.name, registry.servers[matchingOffender.name]]
1872
- : getHubServerEntry(registry);
1873
- const desired = buildDesiredServerRecord(
1874
- hubServerName,
1875
- hubServerConfig,
1876
- resolvedPath,
1877
- );
1878
- replacement = { name: desired.name, ...redactServerConfig(desired.config) };
1879
- updates.push(desired);
1880
- }
1881
-
1882
- const result = updateJsonConfig(resolvedPath, updates, removals);
1883
- return {
1884
- action,
1885
- modified: result.modified,
1886
- backupPath,
1887
- removedServers: removals,
1888
- replacement,
1889
- warnings: updates.flatMap((update) => update.warnings || []),
1890
- };
1891
- }
1892
-
1893
1672
  export function resolveHubUrl() {
1894
1673
  const registryState = inspectRegistry();
1895
1674
  const registry = registryState.valid
@@ -1981,13 +1760,7 @@ export function addRegistryServer(name, url, options = {}) {
1981
1760
  : cloneDefaultRegistry();
1982
1761
  const transport =
1983
1762
  options.transport || (trimmedName === "tfx-hub" ? "hub-url" : "http");
1984
- const policy =
1985
- options.policy ||
1986
- (transport === "stdio"
1987
- ? "stdio"
1988
- : options.gateway_port || options.port
1989
- ? "gateway-http"
1990
- : "hosted");
1763
+ const policy = options.policy || (transport === "stdio" ? "stdio" : "hosted");
1991
1764
 
1992
1765
  registry.servers[trimmedName] = {
1993
1766
  policy,
@@ -2127,26 +1900,6 @@ export function syncRegistryTargets(options = {}) {
2127
1900
  status: "invalid-config",
2128
1901
  message: snapshot.parseError.message,
2129
1902
  });
2130
- continue;
2131
- }
2132
-
2133
- if (
2134
- snapshot.stdioServers.length > 0 &&
2135
- !isClaudeUserConfig(target.filePath)
2136
- ) {
2137
- const remediation = remediate(target.filePath, snapshot.stdioServers, {
2138
- ...registry.policies,
2139
- registry,
2140
- });
2141
- actions.push({
2142
- type: "remediate",
2143
- filePath: target.filePath,
2144
- label: target.label,
2145
- status: remediation.modified ? "updated" : "warning",
2146
- removedServers: remediation.removedServers,
2147
- replacement: remediation.replacement || null,
2148
- warnings: remediation.warnings || [],
2149
- });
2150
1903
  }
2151
1904
  }
2152
1905
 
@@ -2175,6 +1928,26 @@ export function syncRegistryTargets(options = {}) {
2175
1928
  if (serverConfig?.safe !== true) continue;
2176
1929
  if (!serverAppliesToClient(serverConfig, target.client)) continue;
2177
1930
 
1931
+ if (
1932
+ isCodexConfig(target.filePath) &&
1933
+ serverPolicy(serverConfig) === "stdio"
1934
+ ) {
1935
+ const remappedEnv = Object.entries(
1936
+ normalizeEnvDescriptors(serverConfig.env),
1937
+ ).find(([key, descriptor]) => key !== descriptor.env);
1938
+ if (remappedEnv) {
1939
+ actions.push({
1940
+ type: "sync",
1941
+ filePath: target.filePath,
1942
+ label: target.label,
1943
+ status: "warning",
1944
+ server: name,
1945
+ message: `${name}: Codex env_vars는 ${remappedEnv[1].env}를 ${remappedEnv[0]}로 이름 변경할 수 없습니다`,
1946
+ });
1947
+ continue;
1948
+ }
1949
+ }
1950
+
2178
1951
  const denyKey = syncDenylistKey(target.client, name);
2179
1952
  if (denylist.has(denyKey)) {
2180
1953
  const existing = snapshot.servers.find(
@@ -1,6 +1,6 @@
1
1
  // scripts/lib/mcp-manifest.mjs
2
2
  // MCP 서버 활성화 매니페스트 — 단일 진실 소스.
3
- // tfx-setup 위저드가 저장하고, gateway/filter가 참조한다.
3
+ // tfx-setup 위저드가 저장하고, MCP 필터가 참조한다.
4
4
 
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { homedir } from "node:os";
@@ -13,7 +13,7 @@ export const MANIFEST_PATH = join(
13
13
  "mcp-enabled.json",
14
14
  );
15
15
 
16
- /** API 키 불필요 — 항상 활성화 for gateway-managed MCP servers (현재 없음 — serena는 2026-06-10 core에서 제거) */
16
+ /** API 키 불필요 서버 (현재 없음) */
17
17
  export const CORE_SERVERS = Object.freeze([]);
18
18
 
19
19
  /** 검색 MCP — API 키 필요 */
@@ -1,59 +0,0 @@
1
- import { join } from "node:path";
2
-
3
- export const SESSION_TTL_SEC = 30 * 60;
4
- export const STATE_REL_PATH = join(".triflux", "state", "cross-review.json");
5
-
6
- export function readStdin() {
7
- return new Promise((resolve) => {
8
- let raw = "";
9
- process.stdin.setEncoding("utf8");
10
- process.stdin.on("data", (chunk) => {
11
- raw += chunk;
12
- });
13
- process.stdin.on("end", () => resolve(raw));
14
- process.stdin.on("error", () => resolve(""));
15
- });
16
- }
17
-
18
- export function parseJson(raw) {
19
- try {
20
- return JSON.parse(raw);
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- export function nowSec() {
27
- return Math.floor(Date.now() / 1000);
28
- }
29
-
30
- export function resolveBaseDir(payload) {
31
- if (typeof payload?.cwd === "string" && payload.cwd.trim())
32
- return payload.cwd;
33
- if (typeof payload?.directory === "string" && payload.directory.trim())
34
- return payload.directory;
35
- return process.cwd();
36
- }
37
-
38
- export function shouldTrackPath(filePath) {
39
- if (typeof filePath !== "string" || !filePath.trim()) return false;
40
-
41
- const lower = filePath.toLowerCase();
42
- if (
43
- lower.startsWith(".triflux/") ||
44
- lower.startsWith(".omc/") ||
45
- lower.startsWith(".claude/")
46
- )
47
- return false;
48
- if (lower === "package-lock.json" || lower.endsWith("/package-lock.json"))
49
- return false;
50
- if (/\.(md|lock|yml|yaml)$/i.test(lower)) return false;
51
- return true;
52
- }
53
-
54
- export function expectedReviewer(author) {
55
- if (author === "claude") return "codex";
56
- if (author === "codex") return "claude";
57
- if (author === "gemini") return "claude";
58
- return "";
59
- }
@@ -1,16 +0,0 @@
1
- export function nudge(message) {
2
- process.stdout.write(
3
- JSON.stringify({
4
- hookSpecificOutput: {
5
- hookEventName: "PreToolUse",
6
- additionalContext: message,
7
- },
8
- }),
9
- );
10
- process.exit(0);
11
- }
12
-
13
- export function deny(reason) {
14
- process.stderr.write(reason);
15
- process.exit(2);
16
- }