@aiwg/cli 2026.10.1 → 2026.10.3
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/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +210 -4
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/driver.js +3 -1
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +21 -3
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +49 -32
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { tomlString } from './toml-string.mjs';
|
|
2
|
+
import { isLowerLayerEntry, loadLayered, lowerEntryQualified, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
|
|
3
|
+
import { assertConfigDestination, assertProjectCredentials, classifyMcpDestination, projectCredentialRemedy, writeConfigAtomic, writeConfigTransaction } from './config-file.mjs';
|
|
1
4
|
import { manageOmpMcp } from './omp-config.mjs';
|
|
2
5
|
import { manageGrokBuildMcp } from './grok-build-config.mjs';
|
|
3
6
|
import { replaceServer } from './toml-editor.mjs';
|
|
4
7
|
import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
8
|
+
import { applyJsonToolFilterPlan, claudeSettingsPath, prepareClaudePermissions, planToolFilters } from './tool-filters.mjs';
|
|
9
|
+
import { assertCredentialPolicy, resolveCredentialPolicy, renderCredentialMaps, validateEnvReferenceName } from './credentials.mjs';
|
|
5
10
|
/**
|
|
6
11
|
* MCP Server Registry (Runtime ESM)
|
|
7
12
|
*
|
|
@@ -15,7 +20,8 @@ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
|
15
20
|
* @implements #554
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
|
-
import { readFile
|
|
23
|
+
import { readFile } from 'fs/promises';
|
|
24
|
+
import { qualificationDigest } from '../models/qualification-digest.mjs';
|
|
19
25
|
import { dirname, resolve } from 'path';
|
|
20
26
|
import { homedir } from 'os';
|
|
21
27
|
import { existsSync } from 'fs';
|
|
@@ -73,14 +79,31 @@ function validateCredentialReferences(def) {
|
|
|
73
79
|
throw new Error(`Invalid MCP header environment variable reference "${envName}"`);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
82
|
+
for (const [key, envName] of Object.entries(def.envFrom || {})) {
|
|
83
|
+
if (!ENV_REFERENCE_NAME.test(key)) throw new Error(`Invalid MCP env-from variable name "${key}"`);
|
|
84
|
+
if (!ENV_REFERENCE_NAME.test(envName)) {
|
|
85
|
+
throw new Error(`Invalid MCP env-from environment variable reference "${envName}"`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The typed worker dispatch boundary owns qualification. Legacy runtime edits
|
|
91
|
+
// invalidate its binding without importing the TypeScript policy owner.
|
|
92
|
+
export function workerServerDigest(server) {
|
|
93
|
+
const { workerQualification, description, injectedProviders, addedAt, updatedAt, ...execution } = server;
|
|
94
|
+
return qualificationDigest(execution);
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
export class McpServerRegistry {
|
|
79
98
|
#configDir;
|
|
80
99
|
#cache = null;
|
|
81
100
|
|
|
101
|
+
#layers;
|
|
102
|
+
#layering = null;
|
|
103
|
+
|
|
82
104
|
constructor(configDirOverride) {
|
|
83
|
-
this.#
|
|
105
|
+
this.#layers = resolveConfigLayers(configDirOverride);
|
|
106
|
+
this.#configDir = this.#layers ? this.#layers[this.#layers.length - 1] : resolveConfigDir(configDirOverride);
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
getPath() {
|
|
@@ -90,6 +113,13 @@ export class McpServerRegistry {
|
|
|
90
113
|
async load() {
|
|
91
114
|
if (this.#cache) return this.#cache;
|
|
92
115
|
|
|
116
|
+
if (this.#layers) {
|
|
117
|
+
const layered = await loadLayered(this.#layers, REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
|
|
118
|
+
this.#cache = layered.data;
|
|
119
|
+
this.#layering = layered.layering;
|
|
120
|
+
return this.#cache;
|
|
121
|
+
}
|
|
122
|
+
|
|
93
123
|
const filePath = this.getPath();
|
|
94
124
|
try {
|
|
95
125
|
const content = await readFile(filePath, 'utf-8');
|
|
@@ -103,19 +133,36 @@ export class McpServerRegistry {
|
|
|
103
133
|
this.#cache = { ...DEFAULT_REGISTRY, servers: {} };
|
|
104
134
|
}
|
|
105
135
|
|
|
136
|
+
try {
|
|
137
|
+
resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.#cache = null;
|
|
140
|
+
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
106
143
|
return this.#cache;
|
|
107
144
|
}
|
|
108
145
|
|
|
109
|
-
async save() {
|
|
110
|
-
if (!
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
async save(data = this.#cache, explicitPolicy = false) {
|
|
147
|
+
if (!data) return;
|
|
148
|
+
// A fresh instance has no ownership record yet; load it first so inherited
|
|
149
|
+
// entries and inherited policy are never copied into the write layer.
|
|
150
|
+
let layering = this.#layering;
|
|
151
|
+
if (this.#layers && !layering) {
|
|
152
|
+
await this.load();
|
|
153
|
+
layering = this.#layering;
|
|
154
|
+
}
|
|
155
|
+
await saveLayerData(this.getPath(), data, 'servers', this.#layers, layering, explicitPolicy);
|
|
156
|
+
this.#cache = data;
|
|
157
|
+
if (this.#layers) {
|
|
158
|
+
this.clearCache();
|
|
159
|
+
await this.load();
|
|
160
|
+
}
|
|
114
161
|
}
|
|
115
162
|
|
|
116
163
|
async add(def) {
|
|
117
164
|
validateCredentialReferences(def);
|
|
118
|
-
const data = await this.load();
|
|
165
|
+
const data = structuredClone(await this.load());
|
|
119
166
|
|
|
120
167
|
if (data.servers[def.name]) {
|
|
121
168
|
throw new Error(`Server "${def.name}" already exists. Use "update" to modify it.`);
|
|
@@ -123,27 +170,34 @@ export class McpServerRegistry {
|
|
|
123
170
|
|
|
124
171
|
data.servers[def.name] = {
|
|
125
172
|
...def,
|
|
173
|
+
workerQualification: undefined,
|
|
126
174
|
injectedProviders: [],
|
|
127
175
|
addedAt: new Date().toISOString(),
|
|
128
176
|
updatedAt: new Date().toISOString(),
|
|
129
177
|
};
|
|
130
178
|
|
|
131
|
-
await this.save();
|
|
179
|
+
await this.save(data);
|
|
132
180
|
}
|
|
133
181
|
|
|
134
182
|
async remove(name) {
|
|
135
|
-
const data = await this.load();
|
|
183
|
+
const data = structuredClone(await this.load());
|
|
136
184
|
|
|
137
185
|
if (!data.servers[name]) {
|
|
138
186
|
throw new Error(`Server "${name}" not found.`);
|
|
139
187
|
}
|
|
140
188
|
|
|
189
|
+
if (this.#layering && isLowerLayerEntry(this.#layering, name)) {
|
|
190
|
+
throw new Error(`Server "${name}" is defined in a lower configuration layer; remove it there.`);
|
|
191
|
+
}
|
|
192
|
+
if (this.#layering && lowerEntryQualified(this.#layering, name)) {
|
|
193
|
+
throw new Error(`Server "${name}" overrides a qualified lower-layer entry; removing the override would silently re-expose its qualification. Remove the lower-layer entry in its own layer instead.`);
|
|
194
|
+
}
|
|
141
195
|
delete data.servers[name];
|
|
142
|
-
await this.save();
|
|
196
|
+
await this.save(data);
|
|
143
197
|
}
|
|
144
198
|
|
|
145
199
|
async update(name, updates) {
|
|
146
|
-
const data = await this.load();
|
|
200
|
+
const data = structuredClone(await this.load());
|
|
147
201
|
|
|
148
202
|
if (!data.servers[name]) {
|
|
149
203
|
throw new Error(`Server "${name}" not found.`);
|
|
@@ -156,9 +210,11 @@ export class McpServerRegistry {
|
|
|
156
210
|
updatedAt: new Date().toISOString(),
|
|
157
211
|
};
|
|
158
212
|
validateCredentialReferences(next);
|
|
213
|
+
next.workerQualification = workerServerDigest(next) === workerServerDigest(data.servers[name])
|
|
214
|
+
? data.servers[name].workerQualification : undefined;
|
|
159
215
|
data.servers[name] = next;
|
|
160
216
|
|
|
161
|
-
await this.save();
|
|
217
|
+
await this.save(data);
|
|
162
218
|
}
|
|
163
219
|
|
|
164
220
|
async get(name) {
|
|
@@ -172,7 +228,7 @@ export class McpServerRegistry {
|
|
|
172
228
|
}
|
|
173
229
|
|
|
174
230
|
async recordInjection(name, provider) {
|
|
175
|
-
const data = await this.load();
|
|
231
|
+
const data = structuredClone(await this.load());
|
|
176
232
|
const server = data.servers[name];
|
|
177
233
|
if (!server) return;
|
|
178
234
|
|
|
@@ -183,7 +239,7 @@ export class McpServerRegistry {
|
|
|
183
239
|
server.injectedProviders.push(provider);
|
|
184
240
|
}
|
|
185
241
|
|
|
186
|
-
await this.save();
|
|
242
|
+
await this.save(data);
|
|
187
243
|
}
|
|
188
244
|
|
|
189
245
|
async getInjectedProviders() {
|
|
@@ -197,6 +253,36 @@ export class McpServerRegistry {
|
|
|
197
253
|
return [...providers];
|
|
198
254
|
}
|
|
199
255
|
|
|
256
|
+
async getCredentialPolicy() {
|
|
257
|
+
const data = await this.load();
|
|
258
|
+
return data.credentialPolicy;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The enforced organisation floor for this registry, if any layer below the write layer sets one. */
|
|
262
|
+
async getCredentialPolicyFloor() {
|
|
263
|
+
await this.load();
|
|
264
|
+
return this.#layering?.lowerPolicy;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** The config file of the layer that set the enforced floor, for diagnostics. */
|
|
268
|
+
async getCredentialPolicyFloorSource() {
|
|
269
|
+
await this.load();
|
|
270
|
+
return this.#layering?.lowerPolicySource;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async setCredentialPolicy(policy) {
|
|
274
|
+
const data = structuredClone(await this.load());
|
|
275
|
+
const floorPolicy = this.#layering?.lowerPolicy;
|
|
276
|
+
const floorSource = this.#layering?.lowerPolicySource;
|
|
277
|
+
const effective = resolveCredentialPolicy({ flag: policy, floorPolicy, floorSource, env: {} });
|
|
278
|
+
if (effective !== policy) {
|
|
279
|
+
const location = floorSource ? ` (set in ${floorSource})` : '';
|
|
280
|
+
throw new Error(`Cannot relax MCP credential policy floor "${effective}"${location} to "${policy}".`);
|
|
281
|
+
}
|
|
282
|
+
data.credentialPolicy = policy;
|
|
283
|
+
await this.save(data, true);
|
|
284
|
+
}
|
|
285
|
+
|
|
200
286
|
clearCache() {
|
|
201
287
|
this.#cache = null;
|
|
202
288
|
}
|
|
@@ -206,27 +292,48 @@ export class McpServerRegistry {
|
|
|
206
292
|
// Provider injection logic
|
|
207
293
|
// ============================================
|
|
208
294
|
|
|
209
|
-
function buildServerConfig(server, provider) {
|
|
295
|
+
export function buildServerConfig(server, provider) {
|
|
210
296
|
const mcpDefinition = getMcpInjectionDefinition(provider);
|
|
297
|
+
// TOML providers render references through their own named keys in
|
|
298
|
+
// buildServerToml; unconditional interpolation would refuse them here even
|
|
299
|
+
// though the TOML writer handles them.
|
|
300
|
+
const { env, headers } = mcpDefinition?.serverConfigFormat === 'toml'
|
|
301
|
+
? {}
|
|
302
|
+
: renderCredentialMaps(server, normalizeRuntimeProviderId(provider) || provider);
|
|
211
303
|
|
|
212
304
|
switch (mcpDefinition?.serverConfigFormat) {
|
|
305
|
+
case 'claude-code': {
|
|
306
|
+
if (server.type === 'stdio') {
|
|
307
|
+
return {
|
|
308
|
+
command: server.command,
|
|
309
|
+
args: server.args || [],
|
|
310
|
+
...(env ? { env } : {}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Claude Code skips a url entry that has no type.
|
|
314
|
+
return {
|
|
315
|
+
type: server.type,
|
|
316
|
+
url: server.url,
|
|
317
|
+
...(headers ? { headers } : {}),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
213
320
|
case 'antigravity': {
|
|
214
321
|
if (server.type === 'stdio') {
|
|
215
|
-
return { command: server.command, args: server.args || [], ...(
|
|
322
|
+
return { command: server.command, args: server.args || [], ...(env ? { env } : {}) };
|
|
216
323
|
}
|
|
217
|
-
return { serverUrl: server.url, ...(
|
|
324
|
+
return { serverUrl: server.url, ...(headers ? { headers } : {}) };
|
|
218
325
|
}
|
|
219
326
|
case 'standard': {
|
|
220
327
|
if (server.type === 'stdio') {
|
|
221
328
|
return {
|
|
222
329
|
command: server.command,
|
|
223
330
|
args: server.args || [],
|
|
224
|
-
...(
|
|
331
|
+
...(env ? { env } : {}),
|
|
225
332
|
};
|
|
226
333
|
}
|
|
227
334
|
return {
|
|
228
335
|
url: server.url,
|
|
229
|
-
...(
|
|
336
|
+
...(headers ? { headers } : {}),
|
|
230
337
|
};
|
|
231
338
|
}
|
|
232
339
|
|
|
@@ -237,29 +344,30 @@ function buildServerConfig(server, provider) {
|
|
|
237
344
|
command: server.command,
|
|
238
345
|
args: server.args || [],
|
|
239
346
|
disabled: false,
|
|
240
|
-
...(
|
|
347
|
+
...(env ? { env } : {}),
|
|
241
348
|
};
|
|
242
349
|
}
|
|
243
350
|
return {
|
|
244
351
|
type: server.type,
|
|
245
352
|
url: server.url,
|
|
246
353
|
disabled: false,
|
|
247
|
-
...(
|
|
354
|
+
...(headers ? { headers } : {}),
|
|
248
355
|
};
|
|
249
356
|
}
|
|
250
357
|
|
|
251
358
|
case 'opencode': {
|
|
252
359
|
if (server.type === 'stdio') {
|
|
360
|
+
// opencode names a local server's variables `environment`.
|
|
253
361
|
return {
|
|
254
362
|
type: 'local',
|
|
255
363
|
command: [server.command, ...(server.args || [])],
|
|
256
|
-
...(
|
|
364
|
+
...(env ? { environment: env } : {}),
|
|
257
365
|
};
|
|
258
366
|
}
|
|
259
367
|
return {
|
|
260
368
|
type: 'remote',
|
|
261
369
|
url: server.url,
|
|
262
|
-
...(
|
|
370
|
+
...(headers ? { headers } : {}),
|
|
263
371
|
};
|
|
264
372
|
}
|
|
265
373
|
|
|
@@ -271,20 +379,15 @@ function buildServerConfig(server, provider) {
|
|
|
271
379
|
}
|
|
272
380
|
}
|
|
273
381
|
|
|
274
|
-
function tomlString(value) {
|
|
275
|
-
if (typeof value !== 'string' || [...value].some(char => {
|
|
276
|
-
const point = char.codePointAt(0);
|
|
277
|
-
return point >= 0xd800 && point <= 0xdfff;
|
|
278
|
-
})) throw new Error('TOML values must be strings containing valid Unicode scalar values');
|
|
279
|
-
// JSON escapes align with TOML basic strings except DEL must also be escaped.
|
|
280
|
-
return JSON.stringify(value).replace(/\u007f/g, '\\u007f');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
382
|
function tomlKey(value) {
|
|
284
383
|
return typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
|
|
285
384
|
}
|
|
286
385
|
|
|
287
|
-
function
|
|
386
|
+
function tomlInlineTable(values) {
|
|
387
|
+
return `{ ${Object.entries(values).map(([name, value]) => `${tomlKey(name)} = ${tomlString(value)}`).join(', ')} }`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function buildServerToml(server) {
|
|
288
391
|
const lines = [];
|
|
289
392
|
lines.push(`[mcp_servers.${tomlKey(server.name)}]`);
|
|
290
393
|
|
|
@@ -294,8 +397,21 @@ function buildServerToml(server) {
|
|
|
294
397
|
const argsStr = server.args.map(a => tomlString(a)).join(', ');
|
|
295
398
|
lines.push(`args = [${argsStr}]`);
|
|
296
399
|
}
|
|
400
|
+
if (server.env && Object.keys(server.env).length > 0) lines.push(`env = ${tomlInlineTable(server.env)}`);
|
|
401
|
+
// Codex forwards a variable only under its own name (env_vars).
|
|
402
|
+
const forwarded = Object.entries(server.envFrom || {});
|
|
403
|
+
for (const [key, name] of forwarded) {
|
|
404
|
+
validateEnvReferenceName(name);
|
|
405
|
+
if (key !== name) {
|
|
406
|
+
throw new Error(`MCP server "${server.name}": Codex forwards environment variables under their own name only; env-from ${key}=${name} cannot be rendered. Use ${name}=${name}.`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (forwarded.length > 0) lines.push(`env_vars = [${forwarded.map(([name]) => tomlString(name)).join(', ')}]`);
|
|
297
410
|
} else {
|
|
298
411
|
lines.push(`url = ${tomlString(server.url)}`);
|
|
412
|
+
if (server.headers && Object.keys(server.headers).length > 0) lines.push(`http_headers = ${tomlInlineTable(server.headers)}`);
|
|
413
|
+
for (const name of Object.values(server.headerEnv || {})) validateEnvReferenceName(name);
|
|
414
|
+
if (server.headerEnv && Object.keys(server.headerEnv).length > 0) lines.push(`env_http_headers = ${tomlInlineTable(server.headerEnv)}`);
|
|
299
415
|
}
|
|
300
416
|
|
|
301
417
|
lines.push(`startup_timeout_sec = 10.0`);
|
|
@@ -327,18 +443,39 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
327
443
|
}
|
|
328
444
|
|
|
329
445
|
let allServers = await registry.list();
|
|
330
|
-
if (serverFilter
|
|
446
|
+
if (serverFilter !== undefined) {
|
|
331
447
|
allServers = allServers.filter(s => serverFilter.includes(s.name));
|
|
332
448
|
}
|
|
333
449
|
|
|
450
|
+
const toolPlan = options.toolFilters
|
|
451
|
+
? planToolFilters(provider, allServers.map(server => server.name), options.toolFilters)
|
|
452
|
+
: null;
|
|
453
|
+
if (toolPlan) result.warnings = toolPlan.warnings;
|
|
334
454
|
if (allServers.length === 0) {
|
|
335
455
|
result.error = 'No servers to inject. Use "aiwg mcp add" first.';
|
|
336
456
|
return result;
|
|
337
457
|
}
|
|
338
458
|
|
|
459
|
+
assertCredentialPolicy(allServers, resolveCredentialPolicy({
|
|
460
|
+
flag: options.credentialPolicy, registryPolicy: await registry.getCredentialPolicy(),
|
|
461
|
+
floorPolicy: await registry.getCredentialPolicyFloor(),
|
|
462
|
+
floorSource: await registry.getCredentialPolicyFloorSource(),
|
|
463
|
+
}));
|
|
464
|
+
|
|
465
|
+
// The shareable-project decision follows the actual resolved destination,
|
|
466
|
+
// never the requested scope: a `--scope user` request for a provider without
|
|
467
|
+
// a user-scope destination still resolves to the project file and is refused.
|
|
468
|
+
const { userScope, projectRoot } = classifyMcpDestination(provider, options.scope, configPath, projectDir);
|
|
469
|
+
await assertConfigDestination(configPath, projectRoot);
|
|
470
|
+
// Every destination inside the project directory is meant to be committed to
|
|
471
|
+
// version control; home destinations are private, so literals there are not
|
|
472
|
+
// copied into a shared file.
|
|
473
|
+
if (projectRoot) {
|
|
474
|
+
assertProjectCredentials(allServers, configPath, projectCredentialRemedy(provider, projectDir));
|
|
475
|
+
}
|
|
339
476
|
if (normalizedProvider === 'omp') {
|
|
340
477
|
try {
|
|
341
|
-
const managed = await manageOmpMcp(configPath, allServers, { dryRun });
|
|
478
|
+
const managed = await manageOmpMcp(configPath, allServers, { dryRun, userScope, projectRoot });
|
|
342
479
|
if (!dryRun) for (const server of allServers) await registry.recordInjection(server.name, 'omp');
|
|
343
480
|
return { ...result, ...managed };
|
|
344
481
|
} catch (error) {
|
|
@@ -350,6 +487,7 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
350
487
|
try {
|
|
351
488
|
const managed = await manageGrokBuildMcp(configPath, allServers, {
|
|
352
489
|
dryRun,
|
|
490
|
+
userScope,
|
|
353
491
|
root: options.scope === 'user'
|
|
354
492
|
? dirname(dirname(configPath))
|
|
355
493
|
: resolve(projectDir),
|
|
@@ -365,13 +503,13 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
365
503
|
}
|
|
366
504
|
|
|
367
505
|
if (mcpDefinition?.configFormat === 'toml') {
|
|
368
|
-
return injectToml(registry, allServers, configPath, provider, dryRun, result);
|
|
506
|
+
return injectToml(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan);
|
|
369
507
|
}
|
|
370
508
|
|
|
371
|
-
return injectJson(registry, allServers, configPath, provider, dryRun, result);
|
|
509
|
+
return injectJson(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan, { projectDir, scope: options.scope });
|
|
372
510
|
}
|
|
373
511
|
|
|
374
|
-
async function injectJson(registry, servers, configPath, provider, dryRun, result) {
|
|
512
|
+
async function injectJson(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null, location = {}) {
|
|
375
513
|
let existing = {};
|
|
376
514
|
try {
|
|
377
515
|
const content = await readFile(configPath, 'utf-8');
|
|
@@ -404,10 +542,24 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
404
542
|
}
|
|
405
543
|
|
|
406
544
|
const merged = { ...existing, [mcpKey]: newServers };
|
|
545
|
+
if (toolPlan) {
|
|
546
|
+
applyJsonToolFilterPlan(merged, mcpKey, toolPlan);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const isClaude = normalizeRuntimeProviderId(provider) === 'claude-code';
|
|
550
|
+
const settingsPath = isClaude ? claudeSettingsPath(location.projectDir, location.scope) : null;
|
|
551
|
+
const settings = settingsPath ? await prepareClaudePermissions(
|
|
552
|
+
settingsPath, toolPlan?.claudePermissions || { deny: [], allow: [] },
|
|
553
|
+
{ userScope, projectRoot, managedDir: dirname(registry.getPath()), mcpPath: configPath },
|
|
554
|
+
) : null;
|
|
555
|
+
if (settings?.active && settingsPath) result.settingsPath = settingsPath;
|
|
556
|
+
if (settings?.warnings.length) result.warnings = [...(result.warnings || []), ...settings.warnings];
|
|
407
557
|
|
|
408
558
|
if (!dryRun) {
|
|
409
|
-
await
|
|
410
|
-
|
|
559
|
+
await writeConfigTransaction([
|
|
560
|
+
{ file: configPath, content: JSON.stringify(merged, null, 2) + '\n', options: { userScope, projectRoot } },
|
|
561
|
+
...(settings?.writes || []),
|
|
562
|
+
]);
|
|
411
563
|
|
|
412
564
|
for (const server of servers) {
|
|
413
565
|
if (normalizeRuntimeProviderId(provider) === 'antigravity' && !result.serversInjected.includes(server.name)) continue;
|
|
@@ -415,10 +567,11 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
415
567
|
}
|
|
416
568
|
}
|
|
417
569
|
|
|
570
|
+
|
|
418
571
|
return result;
|
|
419
572
|
}
|
|
420
573
|
|
|
421
|
-
async function injectToml(registry, servers, configPath, provider, dryRun, result) {
|
|
574
|
+
async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
|
|
422
575
|
let existing = '';
|
|
423
576
|
try {
|
|
424
577
|
existing = await readFile(configPath, 'utf-8');
|
|
@@ -427,15 +580,15 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
|
|
|
427
580
|
}
|
|
428
581
|
|
|
429
582
|
for (const server of servers) {
|
|
430
|
-
const
|
|
583
|
+
const filterLines = toolPlan?.tomlLines[server.name] || [];
|
|
584
|
+
const edited = replaceServer(existing, server.name, [buildServerToml(server), ...filterLines].join('\n'));
|
|
431
585
|
existing = edited.text;
|
|
432
586
|
if (edited.alreadyPresent) result.alreadyPresent.push(server.name);
|
|
433
587
|
result.serversInjected.push(server.name);
|
|
434
588
|
}
|
|
435
589
|
|
|
436
590
|
if (!dryRun) {
|
|
437
|
-
await
|
|
438
|
-
await writeFile(configPath, existing, 'utf-8');
|
|
591
|
+
await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
|
|
439
592
|
|
|
440
593
|
for (const server of servers) {
|
|
441
594
|
await registry.recordInjection(server.name, provider);
|