@triflux/core 10.1.0 → 10.25.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/hooks/agent-route-guard.mjs +14 -10
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +20 -8
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +267 -35
- package/hooks/hook-registry.json +343 -256
- package/hooks/hooks.json +49 -1
- package/hooks/keyword-rules.json +183 -23
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/permission-safe-allow.mjs +233 -0
- package/hooks/pipeline-stop.mjs +369 -22
- package/hooks/post-tool-tips.mjs +306 -0
- package/hooks/pre-compact-snapshot.mjs +181 -0
- package/hooks/safety-guard.mjs +254 -33
- package/hooks/session-end-cleanup.mjs +345 -0
- package/hooks/session-start-fast.mjs +214 -0
- package/hooks/subagent-tracker.mjs +267 -0
- package/hooks/subagent-verifier.mjs +9 -4
- package/hub/account-broker.mjs +1036 -66
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +96 -58
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +163 -126
- package/hub/bridge.mjs +831 -290
- package/hub/cli-adapter-base.mjs +321 -97
- package/hub/codex-adapter.mjs +179 -93
- package/hub/codex-compat.mjs +4 -4
- package/hub/codex-preflight.mjs +113 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/fullcycle.mjs +34 -18
- package/hub/gemini-adapter.mjs +77 -95
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +167 -38
- package/hub/lib/bash-path.mjs +73 -0
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/env-detect.mjs +145 -0
- package/hub/lib/hosts-compat.mjs +319 -0
- package/hub/lib/mcp-response-cache.mjs +239 -0
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +165 -0
- package/hub/lib/phase-manager.mjs +493 -0
- package/hub/lib/process-utils.mjs +797 -104
- package/hub/lib/prompt-tmp.mjs +33 -0
- package/hub/lib/risk-tier.mjs +53 -0
- package/hub/lib/spawn-trace.mjs +561 -0
- package/hub/lib/ssh-command.mjs +44 -24
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/staged-file-detect.mjs +43 -0
- package/hub/lib/state-snapshot.mjs +293 -0
- package/hub/lib/tfx-route-args.mjs +309 -0
- package/hub/lib/timeline-adapter.mjs +190 -0
- package/hub/lib/trace-recorder.mjs +153 -0
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/quota-middleware.mjs +70 -0
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +65 -48
- package/hub/quality/deslop.mjs +70 -49
- package/hub/reflexion.mjs +87 -13
- package/hub/research.mjs +33 -25
- package/hub/router.mjs +941 -791
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/session-fingerprint.mjs +127 -61
- package/hub/state.mjs +86 -44
- package/hub/team/retry-state-machine.mjs +267 -0
- package/hub/token-mode.mjs +72 -71
- package/hub/workers/worker-utils.mjs +22 -16
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +184 -27
- package/hud/context-monitor.mjs +133 -44
- package/hud/hud-qos-status.mjs +153 -50
- package/hud/mission-board.mjs +56 -0
- package/hud/providers/claude.mjs +398 -114
- package/hud/providers/codex.mjs +96 -21
- package/hud/providers/gemini.mjs +184 -51
- package/hud/renderers.mjs +413 -123
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +81 -39
- package/mesh/index.mjs +69 -0
- package/mesh/mesh-budget.mjs +137 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +163 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +77 -0
- package/package.json +5 -1
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +13 -5
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +58 -30
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +48 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +391 -247
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +1281 -115
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +24 -14
- package/scripts/lib/mcp-server-catalog.mjs +355 -118
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +70 -10
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +32 -7
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
// scripts/lib/mcp-filter.mjs
|
|
3
3
|
// 역할/컨텍스트 기반 MCP 도구 노출 정책의 단일 소스.
|
|
4
4
|
|
|
5
|
-
import { readFileSync } from
|
|
6
|
-
import process from
|
|
7
|
-
import { fileURLToPath } from
|
|
8
|
-
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { CORE_SERVERS, readManifest } from "./mcp-manifest.mjs";
|
|
9
9
|
import {
|
|
10
10
|
DOMAIN_TAG_KEYWORDS,
|
|
11
11
|
MCP_SERVER_TOOL_CATALOG,
|
|
12
|
+
normalizeServerMetadata,
|
|
12
13
|
SEARCH_SERVER_ORDER,
|
|
13
14
|
SERVER_EXPLICIT_KEYWORDS,
|
|
14
|
-
normalizeServerMetadata,
|
|
15
15
|
uniqueStrings,
|
|
16
|
-
} from
|
|
17
|
-
import { readManifest, CORE_SERVERS } from './mcp-manifest.mjs';
|
|
16
|
+
} from "./mcp-server-catalog.mjs";
|
|
18
17
|
|
|
19
|
-
export const KNOWN_MCP_SERVERS = Object.freeze(
|
|
18
|
+
export const KNOWN_MCP_SERVERS = Object.freeze(
|
|
19
|
+
Object.keys(MCP_SERVER_TOOL_CATALOG),
|
|
20
|
+
);
|
|
20
21
|
|
|
21
22
|
const SEARCH_INTENT_PATTERNS = Object.freeze([
|
|
22
23
|
/\b(search|web|browse|look ?up|find|latest|recent|news|current|today|release(?: note)?s?|changelog|announcement|pricing|status|verify|fact[- ]?check)\b/i,
|
|
@@ -25,102 +26,114 @@ const SEARCH_INTENT_PATTERNS = Object.freeze([
|
|
|
25
26
|
|
|
26
27
|
const PROFILE_DEFINITIONS = Object.freeze({
|
|
27
28
|
default: Object.freeze({
|
|
28
|
-
description:
|
|
29
|
-
allowedServers: Object.freeze([
|
|
30
|
-
alwaysOnServers: Object.freeze([
|
|
29
|
+
description: "보수적 기본 프로필. 문서 조회 + 최소 검색만 허용",
|
|
30
|
+
allowedServers: Object.freeze(["context7", "brave-search"]),
|
|
31
|
+
alwaysOnServers: Object.freeze(["context7"]),
|
|
31
32
|
maxSearchServers: 1,
|
|
32
33
|
allowedToolsByServer: Object.freeze({
|
|
33
|
-
context7: Object.freeze([
|
|
34
|
-
|
|
34
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
35
|
+
"brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
|
|
35
36
|
}),
|
|
36
37
|
}),
|
|
37
38
|
executor: Object.freeze({
|
|
38
|
-
description:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
description:
|
|
40
|
+
"구현 워커용. 문서 조회 전용 MCP만 허용 (검색/브라우징은 Codex stall 유발)",
|
|
41
|
+
allowedServers: Object.freeze(["context7"]),
|
|
42
|
+
alwaysOnServers: Object.freeze(["context7"]),
|
|
43
|
+
maxSearchServers: 0,
|
|
42
44
|
allowedToolsByServer: Object.freeze({
|
|
43
|
-
context7: Object.freeze([
|
|
44
|
-
'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
|
|
45
|
-
exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
|
|
46
|
-
tavily: Object.freeze(['tavily_search', 'tavily_extract']),
|
|
47
|
-
playwright: Object.freeze([
|
|
48
|
-
'browser_navigate',
|
|
49
|
-
'browser_navigate_back',
|
|
50
|
-
'browser_snapshot',
|
|
51
|
-
'browser_take_screenshot',
|
|
52
|
-
'browser_wait_for',
|
|
53
|
-
]),
|
|
45
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
54
46
|
}),
|
|
55
47
|
}),
|
|
56
48
|
designer: Object.freeze({
|
|
57
|
-
description:
|
|
58
|
-
allowedServers: Object.freeze([
|
|
59
|
-
|
|
49
|
+
description: "디자인/UI 워커용. 브라우저 관찰 + 문서 조회 중심 MCP 허용",
|
|
50
|
+
allowedServers: Object.freeze([
|
|
51
|
+
"context7",
|
|
52
|
+
"playwright",
|
|
53
|
+
"tavily",
|
|
54
|
+
"exa",
|
|
55
|
+
"brave-search",
|
|
56
|
+
]),
|
|
57
|
+
alwaysOnServers: Object.freeze(["context7"]),
|
|
60
58
|
maxSearchServers: 2,
|
|
61
59
|
allowedToolsByServer: Object.freeze({
|
|
62
|
-
context7: Object.freeze([
|
|
63
|
-
|
|
64
|
-
exa: Object.freeze([
|
|
65
|
-
tavily: Object.freeze([
|
|
60
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
61
|
+
"brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
|
|
62
|
+
exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
|
|
63
|
+
tavily: Object.freeze(["tavily_search", "tavily_extract"]),
|
|
66
64
|
playwright: Object.freeze([
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
"browser_navigate",
|
|
66
|
+
"browser_navigate_back",
|
|
67
|
+
"browser_snapshot",
|
|
68
|
+
"browser_take_screenshot",
|
|
69
|
+
"browser_wait_for",
|
|
72
70
|
]),
|
|
73
71
|
}),
|
|
74
72
|
}),
|
|
75
73
|
analyze: Object.freeze({
|
|
76
|
-
description:
|
|
77
|
-
allowedServers: Object.freeze([
|
|
78
|
-
|
|
74
|
+
description: "분석/설계 워커용. 추론 + 검색 MCP 허용",
|
|
75
|
+
allowedServers: Object.freeze([
|
|
76
|
+
"context7",
|
|
77
|
+
"brave-search",
|
|
78
|
+
"tavily",
|
|
79
|
+
"exa",
|
|
80
|
+
"sequential-thinking",
|
|
81
|
+
]),
|
|
82
|
+
alwaysOnServers: Object.freeze(["context7", "sequential-thinking"]),
|
|
79
83
|
maxSearchServers: 2,
|
|
80
84
|
allowedToolsByServer: Object.freeze({
|
|
81
|
-
context7: Object.freeze([
|
|
82
|
-
|
|
83
|
-
exa: Object.freeze([
|
|
84
|
-
tavily: Object.freeze([
|
|
85
|
-
|
|
85
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
86
|
+
"brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
|
|
87
|
+
exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
|
|
88
|
+
tavily: Object.freeze(["tavily_search", "tavily_extract"]),
|
|
89
|
+
"sequential-thinking": Object.freeze(["sequentialthinking"]),
|
|
86
90
|
}),
|
|
87
91
|
}),
|
|
88
92
|
explore: Object.freeze({
|
|
89
|
-
description:
|
|
90
|
-
allowedServers: Object.freeze([
|
|
91
|
-
|
|
93
|
+
description: "탐색/리서치 워커용. 읽기/검색 중심 MCP만 허용",
|
|
94
|
+
allowedServers: Object.freeze([
|
|
95
|
+
"context7",
|
|
96
|
+
"brave-search",
|
|
97
|
+
"tavily",
|
|
98
|
+
"exa",
|
|
99
|
+
]),
|
|
100
|
+
alwaysOnServers: Object.freeze(["context7"]),
|
|
92
101
|
maxSearchServers: 2,
|
|
93
102
|
allowedToolsByServer: Object.freeze({
|
|
94
|
-
context7: Object.freeze([
|
|
95
|
-
|
|
96
|
-
exa: Object.freeze([
|
|
97
|
-
tavily: Object.freeze([
|
|
103
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
104
|
+
"brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
|
|
105
|
+
exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
|
|
106
|
+
tavily: Object.freeze(["tavily_search", "tavily_extract"]),
|
|
98
107
|
}),
|
|
99
108
|
}),
|
|
100
109
|
reviewer: Object.freeze({
|
|
101
|
-
description:
|
|
102
|
-
allowedServers: Object.freeze([
|
|
103
|
-
|
|
110
|
+
description: "리뷰 워커용. 문서 조회 + 분석 전용 MCP만 허용",
|
|
111
|
+
allowedServers: Object.freeze([
|
|
112
|
+
"context7",
|
|
113
|
+
"brave-search",
|
|
114
|
+
"sequential-thinking",
|
|
115
|
+
]),
|
|
116
|
+
alwaysOnServers: Object.freeze(["context7", "sequential-thinking"]),
|
|
104
117
|
maxSearchServers: 1,
|
|
105
118
|
allowedToolsByServer: Object.freeze({
|
|
106
|
-
context7: Object.freeze([
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
120
|
+
"brave-search": Object.freeze(["brave_web_search"]),
|
|
121
|
+
"sequential-thinking": Object.freeze(["sequentialthinking"]),
|
|
109
122
|
}),
|
|
110
123
|
}),
|
|
111
124
|
writer: Object.freeze({
|
|
112
|
-
description:
|
|
113
|
-
allowedServers: Object.freeze([
|
|
114
|
-
alwaysOnServers: Object.freeze([
|
|
125
|
+
description: "문서/작성 워커용. 공식 문서와 최소 검색 MCP만 허용",
|
|
126
|
+
allowedServers: Object.freeze(["context7", "brave-search", "exa"]),
|
|
127
|
+
alwaysOnServers: Object.freeze(["context7"]),
|
|
115
128
|
maxSearchServers: 2,
|
|
116
129
|
allowedToolsByServer: Object.freeze({
|
|
117
|
-
context7: Object.freeze([
|
|
118
|
-
|
|
119
|
-
exa: Object.freeze([
|
|
130
|
+
context7: Object.freeze(["resolve-library-id", "query-docs"]),
|
|
131
|
+
"brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
|
|
132
|
+
exa: Object.freeze(["web_search_exa"]),
|
|
120
133
|
}),
|
|
121
134
|
}),
|
|
122
135
|
none: Object.freeze({
|
|
123
|
-
description:
|
|
136
|
+
description: "모든 선택적 MCP 서버 비활성화",
|
|
124
137
|
allowedServers: Object.freeze([]),
|
|
125
138
|
alwaysOnServers: Object.freeze([]),
|
|
126
139
|
maxSearchServers: 0,
|
|
@@ -134,110 +147,111 @@ const PROFILE_DEFINITIONS = Object.freeze({
|
|
|
134
147
|
*/
|
|
135
148
|
export const PHASE_OVERRIDES = Object.freeze({
|
|
136
149
|
plan: Object.freeze({
|
|
137
|
-
description:
|
|
138
|
-
allowedServers: Object.freeze([
|
|
139
|
-
blockedServers: Object.freeze([
|
|
150
|
+
description: "계획 단계: 읽기 전용 탐색만 허용",
|
|
151
|
+
allowedServers: Object.freeze(["context7"]),
|
|
152
|
+
blockedServers: Object.freeze(["playwright", "tavily", "exa"]),
|
|
140
153
|
}),
|
|
141
154
|
prd: Object.freeze({
|
|
142
|
-
description:
|
|
143
|
-
allowedServers: Object.freeze([
|
|
144
|
-
blockedServers: Object.freeze([
|
|
155
|
+
description: "PRD 단계: 읽기 전용 탐색 + 문서 조회",
|
|
156
|
+
allowedServers: Object.freeze(["context7", "brave-search"]),
|
|
157
|
+
blockedServers: Object.freeze(["playwright"]),
|
|
145
158
|
}),
|
|
146
159
|
exec: Object.freeze({
|
|
147
|
-
description:
|
|
160
|
+
description: "실행 단계: 프로필 기반 전체 허용 (제한 없음)",
|
|
148
161
|
}),
|
|
149
162
|
verify: Object.freeze({
|
|
150
|
-
description:
|
|
151
|
-
allowedServers: Object.freeze([
|
|
152
|
-
blockedServers: Object.freeze([
|
|
163
|
+
description: "검증 단계: 읽기 전용 + 분석 도구",
|
|
164
|
+
allowedServers: Object.freeze(["context7", "brave-search", "exa"]),
|
|
165
|
+
blockedServers: Object.freeze(["playwright"]),
|
|
153
166
|
}),
|
|
154
167
|
});
|
|
155
168
|
|
|
156
169
|
export const LEGACY_PROFILE_ALIASES = Object.freeze({
|
|
157
|
-
implement:
|
|
158
|
-
analyze:
|
|
159
|
-
review:
|
|
160
|
-
docs:
|
|
161
|
-
minimal:
|
|
170
|
+
implement: "executor",
|
|
171
|
+
analyze: "analyze",
|
|
172
|
+
review: "reviewer",
|
|
173
|
+
docs: "writer",
|
|
174
|
+
minimal: "default",
|
|
162
175
|
});
|
|
163
176
|
|
|
164
177
|
export const SUPPORTED_MCP_PROFILES = Object.freeze([
|
|
165
|
-
|
|
178
|
+
"auto",
|
|
166
179
|
...Object.keys(PROFILE_DEFINITIONS),
|
|
167
180
|
...Object.keys(LEGACY_PROFILE_ALIASES),
|
|
168
181
|
]);
|
|
169
182
|
|
|
170
|
-
function normalizeTaskText(taskText =
|
|
171
|
-
if (typeof taskText !==
|
|
172
|
-
return taskText.replace(/\s+/g,
|
|
183
|
+
function normalizeTaskText(taskText = "") {
|
|
184
|
+
if (typeof taskText !== "string") return "";
|
|
185
|
+
return taskText.replace(/\s+/g, " ").trim();
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
function normalizeProfileName(profile) {
|
|
176
|
-
const raw =
|
|
177
|
-
|
|
189
|
+
const raw =
|
|
190
|
+
typeof profile === "string" && profile.trim() ? profile.trim() : "auto";
|
|
191
|
+
if (raw === "auto") return raw;
|
|
178
192
|
if (PROFILE_DEFINITIONS[raw]) return raw;
|
|
179
193
|
if (LEGACY_PROFILE_ALIASES[raw]) return LEGACY_PROFILE_ALIASES[raw];
|
|
180
194
|
// graceful fallback: --flag나 잘못된 프로필 → 'auto'로 폴백 (hard crash 방지)
|
|
181
|
-
if (raw.startsWith(
|
|
195
|
+
if (raw.startsWith("-") || raw.startsWith("/")) return "auto";
|
|
182
196
|
console.error(`[mcp-filter] 경고: 알 수 없는 프로필 '${raw}', 'auto'로 폴백`);
|
|
183
|
-
return
|
|
197
|
+
return "auto";
|
|
184
198
|
}
|
|
185
199
|
|
|
186
|
-
function resolveAutoProfile(agentType =
|
|
200
|
+
function resolveAutoProfile(agentType = "") {
|
|
187
201
|
switch (agentType) {
|
|
188
|
-
case
|
|
189
|
-
case
|
|
190
|
-
case
|
|
191
|
-
case
|
|
192
|
-
return
|
|
193
|
-
case
|
|
194
|
-
case
|
|
195
|
-
return
|
|
196
|
-
case
|
|
197
|
-
return
|
|
198
|
-
case
|
|
199
|
-
case
|
|
200
|
-
case
|
|
201
|
-
case
|
|
202
|
-
return
|
|
203
|
-
case
|
|
204
|
-
case
|
|
205
|
-
case
|
|
206
|
-
case
|
|
207
|
-
return
|
|
208
|
-
case
|
|
209
|
-
case
|
|
210
|
-
case
|
|
211
|
-
case
|
|
212
|
-
return
|
|
213
|
-
case
|
|
214
|
-
return
|
|
202
|
+
case "executor":
|
|
203
|
+
case "build-fixer":
|
|
204
|
+
case "debugger":
|
|
205
|
+
case "deep-executor":
|
|
206
|
+
return "executor";
|
|
207
|
+
case "test-engineer":
|
|
208
|
+
case "qa-tester":
|
|
209
|
+
return "none";
|
|
210
|
+
case "designer":
|
|
211
|
+
return "designer";
|
|
212
|
+
case "architect":
|
|
213
|
+
case "planner":
|
|
214
|
+
case "critic":
|
|
215
|
+
case "analyst":
|
|
216
|
+
return "analyze";
|
|
217
|
+
case "scientist":
|
|
218
|
+
case "scientist-deep":
|
|
219
|
+
case "document-specialist":
|
|
220
|
+
case "explore":
|
|
221
|
+
return "explore";
|
|
222
|
+
case "code-reviewer":
|
|
223
|
+
case "security-reviewer":
|
|
224
|
+
case "quality-reviewer":
|
|
225
|
+
case "verifier":
|
|
226
|
+
return "reviewer";
|
|
227
|
+
case "writer":
|
|
228
|
+
return "writer";
|
|
215
229
|
default:
|
|
216
|
-
return
|
|
230
|
+
return "default";
|
|
217
231
|
}
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
function escapeRegExp(value) {
|
|
221
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g,
|
|
235
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
function countKeywordMatches(text, keywords = []) {
|
|
225
239
|
let matches = 0;
|
|
226
240
|
for (const keyword of keywords) {
|
|
227
|
-
const source = String(keyword ||
|
|
241
|
+
const source = String(keyword || "").trim();
|
|
228
242
|
if (!source) continue;
|
|
229
243
|
const pattern = /^[a-z0-9- ]+$/i.test(source)
|
|
230
|
-
? new RegExp(`\\b${escapeRegExp(source)}\\b`,
|
|
231
|
-
: new RegExp(escapeRegExp(source),
|
|
244
|
+
? new RegExp(`\\b${escapeRegExp(source)}\\b`, "i")
|
|
245
|
+
: new RegExp(escapeRegExp(source), "iu");
|
|
232
246
|
if (pattern.test(text)) matches += 1;
|
|
233
247
|
}
|
|
234
248
|
return matches;
|
|
235
249
|
}
|
|
236
250
|
|
|
237
|
-
function loadInventory(inventoryFile =
|
|
238
|
-
if (typeof inventoryFile !==
|
|
251
|
+
function loadInventory(inventoryFile = "") {
|
|
252
|
+
if (typeof inventoryFile !== "string" || !inventoryFile.trim()) return null;
|
|
239
253
|
try {
|
|
240
|
-
return JSON.parse(readFileSync(inventoryFile,
|
|
254
|
+
return JSON.parse(readFileSync(inventoryFile, "utf8"));
|
|
241
255
|
} catch {
|
|
242
256
|
return null;
|
|
243
257
|
}
|
|
@@ -245,12 +259,15 @@ function loadInventory(inventoryFile = '') {
|
|
|
245
259
|
|
|
246
260
|
function buildInventoryIndex(inventory = null) {
|
|
247
261
|
const index = new Map();
|
|
248
|
-
if (!inventory || typeof inventory !==
|
|
262
|
+
if (!inventory || typeof inventory !== "object") return index;
|
|
249
263
|
|
|
250
|
-
for (const client of [
|
|
251
|
-
const servers = Array.isArray(inventory[client]?.servers)
|
|
264
|
+
for (const client of ["codex", "gemini"]) {
|
|
265
|
+
const servers = Array.isArray(inventory[client]?.servers)
|
|
266
|
+
? inventory[client].servers
|
|
267
|
+
: [];
|
|
252
268
|
for (const server of servers) {
|
|
253
|
-
if (!server || typeof server.name !==
|
|
269
|
+
if (!server || typeof server.name !== "string" || !server.name.trim())
|
|
270
|
+
continue;
|
|
254
271
|
const name = server.name.trim();
|
|
255
272
|
const previous = index.get(name) || {};
|
|
256
273
|
index.set(name, {
|
|
@@ -278,7 +295,7 @@ function getServerMetadata(server, inventoryIndex) {
|
|
|
278
295
|
});
|
|
279
296
|
}
|
|
280
297
|
|
|
281
|
-
function scoreServer(server, taskText =
|
|
298
|
+
function scoreServer(server, taskText = "", inventoryIndex = new Map()) {
|
|
282
299
|
const normalized = normalizeTaskText(taskText);
|
|
283
300
|
const metadata = getServerMetadata(server, inventoryIndex);
|
|
284
301
|
if (!normalized) {
|
|
@@ -294,14 +311,20 @@ function scoreServer(server, taskText = '', inventoryIndex = new Map()) {
|
|
|
294
311
|
let score = 0;
|
|
295
312
|
const matchedTags = [];
|
|
296
313
|
for (const tag of metadata.domain_tags) {
|
|
297
|
-
const matches = countKeywordMatches(
|
|
314
|
+
const matches = countKeywordMatches(
|
|
315
|
+
normalized,
|
|
316
|
+
DOMAIN_TAG_KEYWORDS[tag] || [],
|
|
317
|
+
);
|
|
298
318
|
if (matches > 0) {
|
|
299
319
|
matchedTags.push(tag);
|
|
300
320
|
score += matches * 2;
|
|
301
321
|
}
|
|
302
322
|
}
|
|
303
323
|
|
|
304
|
-
const explicitMatches = countKeywordMatches(
|
|
324
|
+
const explicitMatches = countKeywordMatches(
|
|
325
|
+
normalized,
|
|
326
|
+
SERVER_EXPLICIT_KEYWORDS[server] || [],
|
|
327
|
+
);
|
|
305
328
|
if (explicitMatches > 0) {
|
|
306
329
|
score += explicitMatches * 4;
|
|
307
330
|
}
|
|
@@ -325,61 +348,86 @@ function compareRankedServers(left, right, workerIndex, availableOrder = []) {
|
|
|
325
348
|
return Number(right.explicitMatch) - Number(left.explicitMatch);
|
|
326
349
|
}
|
|
327
350
|
if (right.score !== left.score) return right.score - left.score;
|
|
328
|
-
if (left.toolCount !== right.toolCount)
|
|
329
|
-
|
|
330
|
-
|
|
351
|
+
if (left.toolCount !== right.toolCount)
|
|
352
|
+
return left.toolCount - right.toolCount;
|
|
353
|
+
|
|
354
|
+
if (
|
|
355
|
+
Number.isInteger(workerIndex) &&
|
|
356
|
+
workerIndex > 0 &&
|
|
357
|
+
availableOrder.length > 1
|
|
358
|
+
) {
|
|
331
359
|
const offset = (workerIndex - 1) % availableOrder.length;
|
|
332
|
-
const rotated = availableOrder
|
|
360
|
+
const rotated = availableOrder
|
|
361
|
+
.slice(offset)
|
|
362
|
+
.concat(availableOrder.slice(0, offset));
|
|
333
363
|
return rotated.indexOf(left.server) - rotated.indexOf(right.server);
|
|
334
364
|
}
|
|
335
365
|
|
|
336
|
-
return
|
|
366
|
+
return (
|
|
367
|
+
availableOrder.indexOf(left.server) - availableOrder.indexOf(right.server)
|
|
368
|
+
);
|
|
337
369
|
}
|
|
338
370
|
|
|
339
371
|
function rankServers(servers = [], options = {}) {
|
|
340
|
-
const inventoryIndex =
|
|
341
|
-
|
|
342
|
-
|
|
372
|
+
const inventoryIndex =
|
|
373
|
+
options.inventoryIndex instanceof Map
|
|
374
|
+
? options.inventoryIndex
|
|
375
|
+
: buildInventoryIndex(options.inventory);
|
|
343
376
|
return servers
|
|
344
377
|
.map((server) => scoreServer(server, options.taskText, inventoryIndex))
|
|
345
|
-
.sort((left, right) =>
|
|
378
|
+
.sort((left, right) =>
|
|
379
|
+
compareRankedServers(left, right, options.workerIndex, servers),
|
|
380
|
+
);
|
|
346
381
|
}
|
|
347
382
|
|
|
348
383
|
function hasContextSignals(servers = [], options = {}) {
|
|
349
384
|
return rankServers(servers, options).some((server) => server.score > 0);
|
|
350
385
|
}
|
|
351
386
|
|
|
352
|
-
function inferPreferredSearchTool(
|
|
387
|
+
function inferPreferredSearchTool(
|
|
388
|
+
taskText = "",
|
|
389
|
+
inventoryIndex = new Map(),
|
|
390
|
+
allowedServers = SEARCH_SERVER_ORDER,
|
|
391
|
+
) {
|
|
353
392
|
const ranked = rankServers(
|
|
354
393
|
SEARCH_SERVER_ORDER.filter((server) => allowedServers.includes(server)),
|
|
355
394
|
{ taskText, inventoryIndex },
|
|
356
395
|
);
|
|
357
|
-
return ranked.find((server) => server.score > 0)?.server ||
|
|
396
|
+
return ranked.find((server) => server.score > 0)?.server || "";
|
|
358
397
|
}
|
|
359
398
|
|
|
360
399
|
function selectContextualServers(baseServers, profile, options = {}) {
|
|
361
400
|
const taskText = normalizeTaskText(options.taskText);
|
|
362
401
|
if (!taskText || !baseServers.length) return [...baseServers];
|
|
363
402
|
|
|
364
|
-
const inventoryIndex =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
403
|
+
const inventoryIndex =
|
|
404
|
+
options.inventoryIndex instanceof Map
|
|
405
|
+
? options.inventoryIndex
|
|
406
|
+
: buildInventoryIndex(options.inventory);
|
|
407
|
+
if (!hasContextSignals(baseServers, { ...options, inventoryIndex }))
|
|
408
|
+
return [...baseServers];
|
|
368
409
|
|
|
369
410
|
const selected = new Set(
|
|
370
|
-
(profile.alwaysOnServers || []).filter((server) =>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
baseServers.includes('playwright')
|
|
374
|
-
&& /(?:browser|screenshot|layout|responsive|visual|screen|page|ui|ux|regression|캡처|스크린샷|레이아웃|반응형|화면|브라우저)/iu.test(taskText)
|
|
411
|
+
(profile.alwaysOnServers || []).filter((server) =>
|
|
412
|
+
baseServers.includes(server),
|
|
413
|
+
),
|
|
375
414
|
);
|
|
415
|
+
const wantsBrowserObservation =
|
|
416
|
+
baseServers.includes("playwright") &&
|
|
417
|
+
/(?:browser|screenshot|layout|responsive|visual|screen|page|ui|ux|regression|캡처|스크린샷|레이아웃|반응형|화면|브라우저)/iu.test(
|
|
418
|
+
taskText,
|
|
419
|
+
);
|
|
376
420
|
if (wantsBrowserObservation) {
|
|
377
|
-
selected.add(
|
|
421
|
+
selected.add("playwright");
|
|
378
422
|
}
|
|
379
|
-
const requestedSearchTool =
|
|
423
|
+
const requestedSearchTool =
|
|
424
|
+
typeof options.searchTool === "string" ? options.searchTool : "";
|
|
380
425
|
|
|
381
426
|
const rankedServers = rankServers(
|
|
382
|
-
baseServers.filter(
|
|
427
|
+
baseServers.filter(
|
|
428
|
+
(server) =>
|
|
429
|
+
!SEARCH_SERVER_ORDER.includes(server) && !selected.has(server),
|
|
430
|
+
),
|
|
383
431
|
{ ...options, inventoryIndex },
|
|
384
432
|
);
|
|
385
433
|
for (const ranked of rankedServers) {
|
|
@@ -399,11 +447,13 @@ function selectContextualServers(baseServers, profile, options = {}) {
|
|
|
399
447
|
taskText,
|
|
400
448
|
{ inventoryIndex },
|
|
401
449
|
);
|
|
402
|
-
const rankedSearchSet = new Set(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
450
|
+
const rankedSearchSet = new Set(
|
|
451
|
+
orderedSearchServers.filter((server) => {
|
|
452
|
+
if (requestedSearchTool === server) return true;
|
|
453
|
+
const ranked = scoreServer(server, taskText, inventoryIndex);
|
|
454
|
+
return ranked.score > 0 || ranked.explicitMatch;
|
|
455
|
+
}),
|
|
456
|
+
);
|
|
407
457
|
const maxSearchServers = Number.isInteger(profile.maxSearchServers)
|
|
408
458
|
? profile.maxSearchServers
|
|
409
459
|
: orderedSearchServers.length;
|
|
@@ -419,12 +469,22 @@ function selectContextualServers(baseServers, profile, options = {}) {
|
|
|
419
469
|
selected.add(server);
|
|
420
470
|
}
|
|
421
471
|
|
|
422
|
-
const alwaysOnServers = baseServers.filter(
|
|
472
|
+
const alwaysOnServers = baseServers.filter(
|
|
473
|
+
(server) =>
|
|
474
|
+
selected.has(server) && (profile.alwaysOnServers || []).includes(server),
|
|
475
|
+
);
|
|
423
476
|
const contextualNonSearch = rankServers(
|
|
424
|
-
baseServers.filter(
|
|
477
|
+
baseServers.filter(
|
|
478
|
+
(server) =>
|
|
479
|
+
selected.has(server) &&
|
|
480
|
+
!SEARCH_SERVER_ORDER.includes(server) &&
|
|
481
|
+
!alwaysOnServers.includes(server),
|
|
482
|
+
),
|
|
425
483
|
{ ...options, inventoryIndex },
|
|
426
484
|
).map((entry) => entry.server);
|
|
427
|
-
const contextualSearch = orderedSearchServers.filter((server) =>
|
|
485
|
+
const contextualSearch = orderedSearchServers.filter((server) =>
|
|
486
|
+
selected.has(server),
|
|
487
|
+
);
|
|
428
488
|
const contextualServers = uniqueStrings([
|
|
429
489
|
...alwaysOnServers,
|
|
430
490
|
...contextualNonSearch,
|
|
@@ -433,34 +493,53 @@ function selectContextualServers(baseServers, profile, options = {}) {
|
|
|
433
493
|
return contextualServers.length ? contextualServers : [...baseServers];
|
|
434
494
|
}
|
|
435
495
|
|
|
436
|
-
export function resolveMcpProfile(agentType =
|
|
496
|
+
export function resolveMcpProfile(agentType = "", requestedProfile = "auto") {
|
|
437
497
|
const normalized = normalizeProfileName(requestedProfile);
|
|
438
|
-
return normalized ===
|
|
498
|
+
return normalized === "auto" ? resolveAutoProfile(agentType) : normalized;
|
|
439
499
|
}
|
|
440
500
|
|
|
441
|
-
export function parseAvailableServers(rawAvailableServers =
|
|
442
|
-
if (Array.isArray(rawAvailableServers))
|
|
443
|
-
|
|
501
|
+
export function parseAvailableServers(rawAvailableServers = "") {
|
|
502
|
+
if (Array.isArray(rawAvailableServers))
|
|
503
|
+
return uniqueStrings(rawAvailableServers);
|
|
504
|
+
if (typeof rawAvailableServers !== "string" || !rawAvailableServers.trim())
|
|
505
|
+
return [];
|
|
444
506
|
return uniqueStrings(rawAvailableServers.split(/[,\s]+/));
|
|
445
507
|
}
|
|
446
508
|
|
|
447
|
-
export function resolveSearchToolOrder(
|
|
448
|
-
|
|
509
|
+
export function resolveSearchToolOrder(
|
|
510
|
+
searchTool = "",
|
|
511
|
+
workerIndex,
|
|
512
|
+
allowedServers = SEARCH_SERVER_ORDER,
|
|
513
|
+
taskText = "",
|
|
514
|
+
options = {},
|
|
515
|
+
) {
|
|
516
|
+
const available = SEARCH_SERVER_ORDER.filter((tool) =>
|
|
517
|
+
allowedServers.includes(tool),
|
|
518
|
+
);
|
|
449
519
|
if (!available.length) return [];
|
|
450
520
|
|
|
451
|
-
const inventoryIndex =
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
521
|
+
const inventoryIndex =
|
|
522
|
+
options.inventoryIndex instanceof Map
|
|
523
|
+
? options.inventoryIndex
|
|
524
|
+
: buildInventoryIndex(options.inventory);
|
|
525
|
+
const preferredSearchTool =
|
|
526
|
+
searchTool && available.includes(searchTool)
|
|
527
|
+
? searchTool
|
|
528
|
+
: inferPreferredSearchTool(taskText, inventoryIndex, available);
|
|
457
529
|
|
|
458
|
-
const ranked = rankServers(available, {
|
|
530
|
+
const ranked = rankServers(available, {
|
|
531
|
+
taskText,
|
|
532
|
+
workerIndex,
|
|
533
|
+
inventoryIndex,
|
|
534
|
+
}).map((entry) => entry.server);
|
|
459
535
|
if (!preferredSearchTool || !available.includes(preferredSearchTool)) {
|
|
460
536
|
return ranked;
|
|
461
537
|
}
|
|
462
538
|
|
|
463
|
-
return [
|
|
539
|
+
return [
|
|
540
|
+
preferredSearchTool,
|
|
541
|
+
...ranked.filter((tool) => tool !== preferredSearchTool),
|
|
542
|
+
];
|
|
464
543
|
}
|
|
465
544
|
|
|
466
545
|
function getProfileDefinition(resolvedProfile) {
|
|
@@ -476,27 +555,43 @@ function applyManifestFilter(servers) {
|
|
|
476
555
|
}
|
|
477
556
|
|
|
478
557
|
export function resolveAllowedServers(options = {}) {
|
|
479
|
-
const resolvedProfile = resolveMcpProfile(
|
|
558
|
+
const resolvedProfile = resolveMcpProfile(
|
|
559
|
+
options.agentType,
|
|
560
|
+
options.requestedProfile,
|
|
561
|
+
);
|
|
480
562
|
const profile = getProfileDefinition(resolvedProfile);
|
|
481
563
|
const availableServers = parseAvailableServers(options.availableServers);
|
|
482
564
|
const inventory = options.inventory || loadInventory(options.inventoryFile);
|
|
483
565
|
const inventoryIndex = buildInventoryIndex(inventory);
|
|
484
566
|
const baseServers = availableServers.length
|
|
485
|
-
? profile.allowedServers.filter((server) =>
|
|
567
|
+
? profile.allowedServers.filter((server) =>
|
|
568
|
+
availableServers.includes(server),
|
|
569
|
+
)
|
|
486
570
|
: [...profile.allowedServers];
|
|
487
571
|
const manifestFiltered = availableServers.length
|
|
488
572
|
? baseServers
|
|
489
573
|
: applyManifestFilter(baseServers);
|
|
490
|
-
return selectContextualServers(manifestFiltered, profile, {
|
|
574
|
+
return selectContextualServers(manifestFiltered, profile, {
|
|
575
|
+
...options,
|
|
576
|
+
inventory,
|
|
577
|
+
inventoryIndex,
|
|
578
|
+
});
|
|
491
579
|
}
|
|
492
580
|
|
|
493
581
|
export function buildPromptHint(options = {}) {
|
|
494
|
-
const resolvedProfile = resolveMcpProfile(
|
|
495
|
-
|
|
582
|
+
const resolvedProfile = resolveMcpProfile(
|
|
583
|
+
options.agentType,
|
|
584
|
+
options.requestedProfile,
|
|
585
|
+
);
|
|
586
|
+
if (resolvedProfile === "none") return "";
|
|
496
587
|
|
|
497
588
|
const inventory = options.inventory || loadInventory(options.inventoryFile);
|
|
498
589
|
const inventoryIndex = buildInventoryIndex(inventory);
|
|
499
|
-
const allowedServers = resolveAllowedServers({
|
|
590
|
+
const allowedServers = resolveAllowedServers({
|
|
591
|
+
...options,
|
|
592
|
+
inventory,
|
|
593
|
+
inventoryIndex,
|
|
594
|
+
});
|
|
500
595
|
const orderedTools = resolveSearchToolOrder(
|
|
501
596
|
options.searchTool,
|
|
502
597
|
Number.isInteger(options.workerIndex) ? options.workerIndex : undefined,
|
|
@@ -505,28 +600,40 @@ export function buildPromptHint(options = {}) {
|
|
|
505
600
|
{ inventory, inventoryIndex },
|
|
506
601
|
);
|
|
507
602
|
const has = (server) => allowedServers.includes(server);
|
|
508
|
-
const orderedSearchHint =
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
603
|
+
const orderedSearchHint =
|
|
604
|
+
orderedTools.length > 1
|
|
605
|
+
? `웹 검색 우선순위: ${orderedTools.join(", ")}.`
|
|
606
|
+
: orderedTools[0]
|
|
607
|
+
? `웹 검색은 ${orderedTools[0]}를 사용하세요.`
|
|
608
|
+
: "";
|
|
609
|
+
const searchFallbackHint =
|
|
610
|
+
orderedTools.length > 1
|
|
611
|
+
? "검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요."
|
|
612
|
+
: "";
|
|
516
613
|
return [
|
|
517
|
-
has(
|
|
518
|
-
has(
|
|
519
|
-
? resolvedProfile ===
|
|
520
|
-
?
|
|
521
|
-
:
|
|
522
|
-
:
|
|
523
|
-
has(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
resolvedProfile
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
614
|
+
has("context7") ? "context7으로 관련 문서를 조회하세요." : "",
|
|
615
|
+
has("playwright")
|
|
616
|
+
? resolvedProfile === "designer"
|
|
617
|
+
? "화면/레이아웃 확인은 playwright를 우선 사용하세요."
|
|
618
|
+
: "브라우저/UI 검증이 필요하면 playwright를 사용하세요."
|
|
619
|
+
: "",
|
|
620
|
+
has("sequential-thinking")
|
|
621
|
+
? "sequential-thinking으로 체계적으로 분석하세요."
|
|
622
|
+
: "",
|
|
623
|
+
resolvedProfile === "reviewer" && orderedTools[0]
|
|
624
|
+
? `외부 근거가 더 필요하면 ${orderedTools[0]}를 사용하세요.`
|
|
625
|
+
: "",
|
|
626
|
+
resolvedProfile !== "reviewer" ? orderedSearchHint : "",
|
|
627
|
+
resolvedProfile !== "reviewer" ? searchFallbackHint : "",
|
|
628
|
+
resolvedProfile === "explore"
|
|
629
|
+
? "검색 깊이를 제한하고 읽기 전용 조사에 집중하세요."
|
|
630
|
+
: "",
|
|
631
|
+
resolvedProfile === "writer"
|
|
632
|
+
? "검색 결과의 출처 URL을 함께 제시하세요."
|
|
633
|
+
: "",
|
|
634
|
+
]
|
|
635
|
+
.filter(Boolean)
|
|
636
|
+
.join(" ");
|
|
530
637
|
}
|
|
531
638
|
|
|
532
639
|
export function getGeminiAllowedServers(options = {}) {
|
|
@@ -535,7 +642,10 @@ export function getGeminiAllowedServers(options = {}) {
|
|
|
535
642
|
|
|
536
643
|
export function getCodexMcpConfig(options = {}) {
|
|
537
644
|
const allowedServers = new Set(resolveAllowedServers(options));
|
|
538
|
-
const resolvedProfile = resolveMcpProfile(
|
|
645
|
+
const resolvedProfile = resolveMcpProfile(
|
|
646
|
+
options.agentType,
|
|
647
|
+
options.requestedProfile,
|
|
648
|
+
);
|
|
539
649
|
// Codex에 실제 등록된 서버만 대상으로 config override 생성.
|
|
540
650
|
// 미등록 서버에 enabled=false를 보내면 "invalid transport" 에러 발생.
|
|
541
651
|
const registeredServers = parseAvailableServers(options.availableServers);
|
|
@@ -546,18 +656,23 @@ export function getCodexMcpConfig(options = {}) {
|
|
|
546
656
|
}
|
|
547
657
|
const targetServers = registeredServers;
|
|
548
658
|
|
|
549
|
-
if (resolvedProfile ===
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
|
|
659
|
+
if (resolvedProfile === "none") {
|
|
660
|
+
// 등록된 서버를 전부 enabled=false로 비활성화.
|
|
661
|
+
// 미등록 서버에 보내면 "invalid transport" 에러지만, registeredServers는 등록 확인 완료.
|
|
662
|
+
const config = { mcp_servers: {} };
|
|
663
|
+
for (const server of targetServers) {
|
|
664
|
+
config.mcp_servers[server] = { enabled: false };
|
|
665
|
+
}
|
|
666
|
+
return config;
|
|
553
667
|
}
|
|
554
668
|
|
|
555
669
|
const config = { mcp_servers: {} };
|
|
556
|
-
const allowedToolsByServer =
|
|
670
|
+
const allowedToolsByServer =
|
|
671
|
+
getProfileDefinition(resolvedProfile).allowedToolsByServer;
|
|
557
672
|
for (const server of targetServers) {
|
|
558
|
-
// Codex 0.115+: transport 없는 서버에 enabled=false를 보내면 "invalid transport" 에러.
|
|
559
|
-
// 비허용 서버는 override에서 제외한다 (Codex 기본 설정이 유지됨).
|
|
560
673
|
if (!allowedServers.has(server)) {
|
|
674
|
+
// 비허용 서버는 명시적으로 비활성화하여 MCP 시작 자체를 방지.
|
|
675
|
+
config.mcp_servers[server] = { enabled: false };
|
|
561
676
|
continue;
|
|
562
677
|
}
|
|
563
678
|
|
|
@@ -571,15 +686,16 @@ export function getCodexMcpConfig(options = {}) {
|
|
|
571
686
|
|
|
572
687
|
function toTomlLiteral(value) {
|
|
573
688
|
if (Array.isArray(value)) {
|
|
574
|
-
return `[${value.map((item) => toTomlLiteral(item)).join(
|
|
689
|
+
return `[${value.map((item) => toTomlLiteral(item)).join(",")}]`;
|
|
575
690
|
}
|
|
576
|
-
if (typeof value ===
|
|
577
|
-
if (typeof value ===
|
|
691
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
692
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
693
|
+
return String(value);
|
|
578
694
|
throw new Error(`지원하지 않는 TOML 값 타입: ${typeof value}`);
|
|
579
695
|
}
|
|
580
696
|
|
|
581
697
|
function flattenConfig(prefix, value, output) {
|
|
582
|
-
if (value && typeof value ===
|
|
698
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
583
699
|
for (const [key, nestedValue] of Object.entries(value)) {
|
|
584
700
|
flattenConfig(prefix ? `${prefix}.${key}` : key, nestedValue, output);
|
|
585
701
|
}
|
|
@@ -591,7 +707,7 @@ function flattenConfig(prefix, value, output) {
|
|
|
591
707
|
export function getCodexConfigOverrides(options = {}) {
|
|
592
708
|
const config = getCodexMcpConfig(options);
|
|
593
709
|
const overrides = [];
|
|
594
|
-
flattenConfig(
|
|
710
|
+
flattenConfig("", config, overrides);
|
|
595
711
|
return overrides;
|
|
596
712
|
}
|
|
597
713
|
|
|
@@ -599,7 +715,10 @@ export function buildMcpPolicy(options = {}) {
|
|
|
599
715
|
const inventory = options.inventory || loadInventory(options.inventoryFile);
|
|
600
716
|
const inventoryIndex = buildInventoryIndex(inventory);
|
|
601
717
|
const resolvedOptions = { ...options, inventory, inventoryIndex };
|
|
602
|
-
const resolvedProfile = resolveMcpProfile(
|
|
718
|
+
const resolvedProfile = resolveMcpProfile(
|
|
719
|
+
options.agentType,
|
|
720
|
+
options.requestedProfile,
|
|
721
|
+
);
|
|
603
722
|
let allowedServers = resolveAllowedServers(resolvedOptions);
|
|
604
723
|
const hint = buildPromptHint(resolvedOptions);
|
|
605
724
|
|
|
@@ -612,9 +731,10 @@ export function buildMcpPolicy(options = {}) {
|
|
|
612
731
|
}
|
|
613
732
|
|
|
614
733
|
return {
|
|
615
|
-
requestedProfile:
|
|
616
|
-
|
|
617
|
-
|
|
734
|
+
requestedProfile:
|
|
735
|
+
typeof options.requestedProfile === "string" && options.requestedProfile
|
|
736
|
+
? options.requestedProfile
|
|
737
|
+
: "auto",
|
|
618
738
|
resolvedProfile,
|
|
619
739
|
resolvedPhase: phase || null,
|
|
620
740
|
allowedServers,
|
|
@@ -630,7 +750,20 @@ function shellEscape(value) {
|
|
|
630
750
|
}
|
|
631
751
|
|
|
632
752
|
function shellArray(name, values) {
|
|
633
|
-
return `${name}=(${values.map((value) => shellEscape(value)).join(
|
|
753
|
+
return `${name}=(${values.map((value) => shellEscape(value)).join(" ")})`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export function toDelimited(policy) {
|
|
757
|
+
const RS = "\x1e";
|
|
758
|
+
return [
|
|
759
|
+
policy.requestedProfile,
|
|
760
|
+
policy.resolvedProfile,
|
|
761
|
+
policy.hint,
|
|
762
|
+
policy.geminiAllowedServers.join(","),
|
|
763
|
+
policy.codexConfigOverrides.flatMap((o) => ["-c", o]).join(","),
|
|
764
|
+
JSON.stringify(policy.codexConfig),
|
|
765
|
+
policy.resolvedPhase || "",
|
|
766
|
+
].join(RS);
|
|
634
767
|
}
|
|
635
768
|
|
|
636
769
|
export function toShellExports(policy) {
|
|
@@ -638,30 +771,33 @@ export function toShellExports(policy) {
|
|
|
638
771
|
`MCP_PROFILE_REQUESTED=${shellEscape(policy.requestedProfile)}`,
|
|
639
772
|
`MCP_RESOLVED_PROFILE=${shellEscape(policy.resolvedProfile)}`,
|
|
640
773
|
`MCP_HINT=${shellEscape(policy.hint)}`,
|
|
641
|
-
shellArray(
|
|
642
|
-
shellArray(
|
|
774
|
+
shellArray("GEMINI_ALLOWED_SERVERS", policy.geminiAllowedServers),
|
|
775
|
+
shellArray(
|
|
776
|
+
"CODEX_CONFIG_FLAGS",
|
|
777
|
+
policy.codexConfigOverrides.flatMap((override) => ["-c", override]),
|
|
778
|
+
),
|
|
643
779
|
`CODEX_CONFIG_JSON=${shellEscape(JSON.stringify(policy.codexConfig))}`,
|
|
644
780
|
];
|
|
645
781
|
if (policy.resolvedPhase) {
|
|
646
782
|
lines.push(`MCP_PIPELINE_PHASE=${shellEscape(policy.resolvedPhase)}`);
|
|
647
783
|
}
|
|
648
|
-
return lines.join(
|
|
784
|
+
return lines.join("\n");
|
|
649
785
|
}
|
|
650
786
|
|
|
651
787
|
function parseCliArgs(argv) {
|
|
652
788
|
const args = {
|
|
653
|
-
command:
|
|
654
|
-
agentType:
|
|
655
|
-
requestedProfile:
|
|
789
|
+
command: "json",
|
|
790
|
+
agentType: "",
|
|
791
|
+
requestedProfile: "auto",
|
|
656
792
|
availableServers: [],
|
|
657
|
-
inventoryFile:
|
|
658
|
-
searchTool:
|
|
659
|
-
taskText:
|
|
793
|
+
inventoryFile: "",
|
|
794
|
+
searchTool: "",
|
|
795
|
+
taskText: "",
|
|
660
796
|
workerIndex: undefined,
|
|
661
797
|
};
|
|
662
798
|
|
|
663
|
-
const [first =
|
|
664
|
-
if (!first.startsWith(
|
|
799
|
+
const [first = "json"] = argv;
|
|
800
|
+
if (!first.startsWith("--")) {
|
|
665
801
|
args.command = first;
|
|
666
802
|
argv = argv.slice(1);
|
|
667
803
|
}
|
|
@@ -676,28 +812,28 @@ function parseCliArgs(argv) {
|
|
|
676
812
|
};
|
|
677
813
|
|
|
678
814
|
switch (token) {
|
|
679
|
-
case
|
|
815
|
+
case "--agent":
|
|
680
816
|
args.agentType = next();
|
|
681
817
|
break;
|
|
682
|
-
case
|
|
818
|
+
case "--profile":
|
|
683
819
|
args.requestedProfile = next();
|
|
684
820
|
break;
|
|
685
|
-
case
|
|
821
|
+
case "--available":
|
|
686
822
|
args.availableServers = parseAvailableServers(next());
|
|
687
823
|
break;
|
|
688
|
-
case
|
|
824
|
+
case "--inventory-file":
|
|
689
825
|
args.inventoryFile = next();
|
|
690
826
|
break;
|
|
691
|
-
case
|
|
827
|
+
case "--search-tool":
|
|
692
828
|
args.searchTool = next();
|
|
693
829
|
break;
|
|
694
|
-
case
|
|
830
|
+
case "--task-text":
|
|
695
831
|
args.taskText = next();
|
|
696
832
|
break;
|
|
697
|
-
case
|
|
833
|
+
case "--worker-index":
|
|
698
834
|
args.workerIndex = Number.parseInt(next(), 10);
|
|
699
835
|
break;
|
|
700
|
-
case
|
|
836
|
+
case "--phase":
|
|
701
837
|
args.phase = next();
|
|
702
838
|
break;
|
|
703
839
|
default:
|
|
@@ -713,7 +849,9 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
713
849
|
try {
|
|
714
850
|
args = parseCliArgs(argv);
|
|
715
851
|
} catch (error) {
|
|
716
|
-
console.error(
|
|
852
|
+
console.error(
|
|
853
|
+
`[mcp-filter] ${error instanceof Error ? error.message : String(error)}`,
|
|
854
|
+
);
|
|
717
855
|
process.exitCode = 64;
|
|
718
856
|
return;
|
|
719
857
|
}
|
|
@@ -722,14 +860,20 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
722
860
|
try {
|
|
723
861
|
policy = buildMcpPolicy(args);
|
|
724
862
|
} catch (error) {
|
|
725
|
-
console.error(
|
|
863
|
+
console.error(
|
|
864
|
+
`[mcp-filter] ${error instanceof Error ? error.message : String(error)}`,
|
|
865
|
+
);
|
|
726
866
|
process.exitCode = 65;
|
|
727
867
|
return;
|
|
728
868
|
}
|
|
729
|
-
if (args.command ===
|
|
869
|
+
if (args.command === "shell") {
|
|
730
870
|
process.stdout.write(`${toShellExports(policy)}\n`);
|
|
731
871
|
return;
|
|
732
872
|
}
|
|
873
|
+
if (args.command === "delimited") {
|
|
874
|
+
process.stdout.write(toDelimited(policy));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
733
877
|
|
|
734
878
|
process.stdout.write(`${JSON.stringify(policy, null, 2)}\n`);
|
|
735
879
|
}
|