@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
package/dist/src/mcp/registry.js
CHANGED
|
@@ -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
|
|
7
12
|
*
|
|
@@ -11,8 +16,11 @@ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
|
11
16
|
*
|
|
12
17
|
* @implements #554
|
|
13
18
|
*/
|
|
14
|
-
import { readFile
|
|
15
|
-
import {
|
|
19
|
+
import { readFile } from 'fs/promises';
|
|
20
|
+
import { qualificationDigest } from '../models/qualification-digest.mjs';
|
|
21
|
+
import { qualifyWorkerInvocationProfile, requiredWorkerControls } from '../models/provider-policy.js';
|
|
22
|
+
import { dirname, isAbsolute, resolve } from 'path';
|
|
23
|
+
import { homedir } from 'os';
|
|
16
24
|
import { resolveConfigDir } from '../config/user-config.js';
|
|
17
25
|
import { getProviderDefinition } from '../providers/provider-definitions.js';
|
|
18
26
|
// ============================================
|
|
@@ -33,12 +41,76 @@ function validateCredentialReferences(def) {
|
|
|
33
41
|
throw new Error(`Invalid MCP header environment variable reference "${envName}"`);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
44
|
+
for (const [key, envName] of Object.entries(def.envFrom ?? {})) {
|
|
45
|
+
if (!ENV_REFERENCE_NAME.test(key))
|
|
46
|
+
throw new Error(`Invalid MCP env-from variable name "${key}"`);
|
|
47
|
+
if (!ENV_REFERENCE_NAME.test(envName)) {
|
|
48
|
+
throw new Error(`Invalid MCP env-from environment variable reference "${envName}"`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function workerServerDigest(server) {
|
|
53
|
+
const { workerQualification, description, injectedProviders, addedAt, updatedAt, ...execution } = server;
|
|
54
|
+
return qualificationDigest(execution);
|
|
55
|
+
}
|
|
56
|
+
function workerQualificationBinding(server, input) {
|
|
57
|
+
if (!Array.isArray(input.requiredControls) || input.requiredControls.some(name => typeof name !== 'string' || !name || !Object.hasOwn(input.adapter.invocationProfiles?.[input.profile]?.controls ?? {}, name))) {
|
|
58
|
+
throw new Error('MCP worker must declare valid required controls');
|
|
59
|
+
}
|
|
60
|
+
if (server.enabled === false || input.mcp.serverRef !== server.name) {
|
|
61
|
+
throw new Error('MCP worker server is disabled or does not match the qualified profile');
|
|
62
|
+
}
|
|
63
|
+
if (input.authorizedServerDigest !== workerServerDigest(server)) {
|
|
64
|
+
throw new Error('MCP server command, endpoint or configuration lacks matching host authorization');
|
|
65
|
+
}
|
|
66
|
+
if (server.type === 'stdio') {
|
|
67
|
+
if (!server.command || !isAbsolute(server.command) || server.command.includes('\0')
|
|
68
|
+
|| (server.args ?? []).some(arg => typeof arg !== 'string' || arg.includes('\0'))) {
|
|
69
|
+
throw new Error('MCP worker requires an authorized absolute local command and typed argv');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
let endpoint;
|
|
74
|
+
try {
|
|
75
|
+
endpoint = new URL(server.url ?? '');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new Error('MCP worker endpoint is invalid');
|
|
79
|
+
}
|
|
80
|
+
if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.hash) {
|
|
81
|
+
throw new Error('MCP worker endpoint must be an authorized HTTP URL without credentials or fragment');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const profile = qualifyWorkerInvocationProfile(input.adapter, { ...input, transport: 'mcp' });
|
|
85
|
+
const installed = input.installationQualification;
|
|
86
|
+
if (installed?.serverDigest !== input.authorizedServerDigest) {
|
|
87
|
+
throw new Error('MCP worker installation does not cover the authorized registration');
|
|
88
|
+
}
|
|
89
|
+
const evidence = profile.installationQualification;
|
|
90
|
+
if (!installed || installed.status !== 'ENABLED' || installed.platform !== 'linux' || installed.cliVersion !== input.cliVersion
|
|
91
|
+
|| !evidence || ['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef'].some(key => installed[key] !== evidence[key])) {
|
|
92
|
+
throw new Error('MCP worker requires current trusted installation qualification');
|
|
93
|
+
}
|
|
94
|
+
if (Object.keys(server.env ?? {}).some(key => !profile.envAllowlist.includes(key))
|
|
95
|
+
|| Object.values(server.headerEnv ?? {}).some(key => !profile.envAllowlist.includes(key))) {
|
|
96
|
+
throw new Error('MCP worker server environment exceeds the qualified profile allowlist');
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
serverDigest: workerServerDigest(server), adapterDigest: qualificationDigest(input.adapter),
|
|
100
|
+
profile: input.profile, cliVersion: input.cliVersion,
|
|
101
|
+
scopeDigest: installed.scopeDigest, fixtureDigest: installed.fixtureDigest,
|
|
102
|
+
requiredControls: requiredWorkerControls(input).sort(),
|
|
103
|
+
};
|
|
36
104
|
}
|
|
37
105
|
export class McpServerRegistry {
|
|
38
106
|
configDir;
|
|
39
107
|
cache = null;
|
|
108
|
+
/** Configuration layers, lowest precedence first; null for a single directory */
|
|
109
|
+
layers;
|
|
110
|
+
layering = null;
|
|
40
111
|
constructor(configDirOverride) {
|
|
41
|
-
this.
|
|
112
|
+
this.layers = resolveConfigLayers(configDirOverride);
|
|
113
|
+
this.configDir = this.layers ? this.layers[this.layers.length - 1] : resolveConfigDir(configDirOverride);
|
|
42
114
|
}
|
|
43
115
|
/** Get the registry file path */
|
|
44
116
|
getPath() {
|
|
@@ -48,6 +120,12 @@ export class McpServerRegistry {
|
|
|
48
120
|
async load() {
|
|
49
121
|
if (this.cache)
|
|
50
122
|
return this.cache;
|
|
123
|
+
if (this.layers) {
|
|
124
|
+
const layered = await loadLayered(this.layers, REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
|
|
125
|
+
this.cache = layered.data;
|
|
126
|
+
this.layering = layered.layering;
|
|
127
|
+
return this.cache;
|
|
128
|
+
}
|
|
51
129
|
const filePath = this.getPath();
|
|
52
130
|
try {
|
|
53
131
|
const content = await readFile(filePath, 'utf-8');
|
|
@@ -61,43 +139,67 @@ export class McpServerRegistry {
|
|
|
61
139
|
catch {
|
|
62
140
|
this.cache = { ...DEFAULT_REGISTRY, servers: {} };
|
|
63
141
|
}
|
|
142
|
+
try {
|
|
143
|
+
resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.cache = null;
|
|
147
|
+
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
148
|
+
}
|
|
64
149
|
return this.cache;
|
|
65
150
|
}
|
|
66
151
|
/** Save the registry to disk */
|
|
67
|
-
async save() {
|
|
68
|
-
if (!
|
|
152
|
+
async save(data = this.cache, explicitPolicy = false) {
|
|
153
|
+
if (!data)
|
|
69
154
|
return;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
155
|
+
// A fresh instance has no ownership record yet; load it first so inherited
|
|
156
|
+
// entries and inherited policy are never copied into the write layer.
|
|
157
|
+
let layering = this.layering;
|
|
158
|
+
if (this.layers && !layering) {
|
|
159
|
+
await this.load();
|
|
160
|
+
layering = this.layering;
|
|
161
|
+
}
|
|
162
|
+
await saveLayerData(this.getPath(), data, 'servers', this.layers, layering, explicitPolicy);
|
|
163
|
+
this.cache = data;
|
|
164
|
+
if (this.layers) {
|
|
165
|
+
this.clearCache();
|
|
166
|
+
await this.load();
|
|
167
|
+
}
|
|
73
168
|
}
|
|
74
169
|
/** Add a new MCP server definition */
|
|
75
170
|
async add(def) {
|
|
76
171
|
validateCredentialReferences(def);
|
|
77
|
-
const data = await this.load();
|
|
172
|
+
const data = structuredClone(await this.load());
|
|
78
173
|
if (data.servers[def.name]) {
|
|
79
174
|
throw new Error(`Server "${def.name}" already exists. Use "update" to modify it.`);
|
|
80
175
|
}
|
|
81
176
|
data.servers[def.name] = {
|
|
82
177
|
...def,
|
|
178
|
+
workerQualification: undefined,
|
|
83
179
|
injectedProviders: [],
|
|
84
180
|
addedAt: new Date().toISOString(),
|
|
85
181
|
updatedAt: new Date().toISOString(),
|
|
86
182
|
};
|
|
87
|
-
await this.save();
|
|
183
|
+
await this.save(data);
|
|
88
184
|
}
|
|
89
185
|
/** Remove an MCP server definition */
|
|
90
186
|
async remove(name) {
|
|
91
|
-
const data = await this.load();
|
|
187
|
+
const data = structuredClone(await this.load());
|
|
92
188
|
if (!data.servers[name]) {
|
|
93
189
|
throw new Error(`Server "${name}" not found.`);
|
|
94
190
|
}
|
|
191
|
+
if (this.layering && isLowerLayerEntry(this.layering, name)) {
|
|
192
|
+
throw new Error(`Server "${name}" is defined in a lower configuration layer; remove it there.`);
|
|
193
|
+
}
|
|
194
|
+
if (this.layering && lowerEntryQualified(this.layering, name)) {
|
|
195
|
+
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.`);
|
|
196
|
+
}
|
|
95
197
|
delete data.servers[name];
|
|
96
|
-
await this.save();
|
|
198
|
+
await this.save(data);
|
|
97
199
|
}
|
|
98
200
|
/** Update an existing MCP server definition */
|
|
99
201
|
async update(name, updates) {
|
|
100
|
-
const data = await this.load();
|
|
202
|
+
const data = structuredClone(await this.load());
|
|
101
203
|
if (!data.servers[name]) {
|
|
102
204
|
throw new Error(`Server "${name}" not found.`);
|
|
103
205
|
}
|
|
@@ -108,8 +210,35 @@ export class McpServerRegistry {
|
|
|
108
210
|
updatedAt: new Date().toISOString(),
|
|
109
211
|
};
|
|
110
212
|
validateCredentialReferences(next);
|
|
213
|
+
// Ordinary registration edits cannot mint qualification, including a copied binding.
|
|
214
|
+
next.workerQualification = workerServerDigest(next) === workerServerDigest(data.servers[name])
|
|
215
|
+
? data.servers[name].workerQualification : undefined;
|
|
111
216
|
data.servers[name] = next;
|
|
217
|
+
await this.save(data);
|
|
218
|
+
}
|
|
219
|
+
/** Bind an already qualified host installation to this exact MCP registration. */
|
|
220
|
+
async recordWorkerQualification(name, input) {
|
|
221
|
+
this.clearCache();
|
|
222
|
+
const server = await this.get(name);
|
|
223
|
+
if (!server)
|
|
224
|
+
throw new Error(`Server "${name}" not found.`);
|
|
225
|
+
const binding = workerQualificationBinding(server, input);
|
|
226
|
+
server.workerQualification = binding;
|
|
112
227
|
await this.save();
|
|
228
|
+
return structuredClone(binding);
|
|
229
|
+
}
|
|
230
|
+
/** Recheck immediately before use; direct shell invocations do not use this registry. */
|
|
231
|
+
async assertWorkerQualified(name, input) {
|
|
232
|
+
// Other trusted registry writers may have changed the registration since load().
|
|
233
|
+
this.clearCache();
|
|
234
|
+
const server = await this.get(name);
|
|
235
|
+
if (!server)
|
|
236
|
+
throw new Error(`Server "${name}" not found.`);
|
|
237
|
+
const current = workerQualificationBinding(server, input);
|
|
238
|
+
if (!server.workerQualification || qualificationDigest(server.workerQualification) !== qualificationDigest(current)) {
|
|
239
|
+
throw new Error('MCP worker registration qualification is missing or changed');
|
|
240
|
+
}
|
|
241
|
+
return structuredClone(server);
|
|
113
242
|
}
|
|
114
243
|
/** Get a specific server definition */
|
|
115
244
|
async get(name) {
|
|
@@ -123,7 +252,7 @@ export class McpServerRegistry {
|
|
|
123
252
|
}
|
|
124
253
|
/** Record that a server was injected into a provider */
|
|
125
254
|
async recordInjection(name, provider) {
|
|
126
|
-
const data = await this.load();
|
|
255
|
+
const data = structuredClone(await this.load());
|
|
127
256
|
const server = data.servers[name];
|
|
128
257
|
if (!server)
|
|
129
258
|
return;
|
|
@@ -133,7 +262,7 @@ export class McpServerRegistry {
|
|
|
133
262
|
if (!server.injectedProviders.includes(provider)) {
|
|
134
263
|
server.injectedProviders.push(provider);
|
|
135
264
|
}
|
|
136
|
-
await this.save();
|
|
265
|
+
await this.save(data);
|
|
137
266
|
}
|
|
138
267
|
/** Get all providers that have had servers injected */
|
|
139
268
|
async getInjectedProviders() {
|
|
@@ -146,6 +275,33 @@ export class McpServerRegistry {
|
|
|
146
275
|
}
|
|
147
276
|
return [...providers];
|
|
148
277
|
}
|
|
278
|
+
/** Registry-wide default credential policy, if one is set */
|
|
279
|
+
async getCredentialPolicy() {
|
|
280
|
+
const data = await this.load();
|
|
281
|
+
return data.credentialPolicy;
|
|
282
|
+
}
|
|
283
|
+
/** The enforced organisation floor for this registry, if any layer below the write layer sets one. */
|
|
284
|
+
async getCredentialPolicyFloor() {
|
|
285
|
+
await this.load();
|
|
286
|
+
return this.layering?.lowerPolicy;
|
|
287
|
+
}
|
|
288
|
+
/** The config file of the layer that set the enforced floor, for diagnostics. */
|
|
289
|
+
async getCredentialPolicyFloorSource() {
|
|
290
|
+
await this.load();
|
|
291
|
+
return this.layering?.lowerPolicySource;
|
|
292
|
+
}
|
|
293
|
+
async setCredentialPolicy(policy) {
|
|
294
|
+
const data = structuredClone(await this.load());
|
|
295
|
+
const floorPolicy = this.layering?.lowerPolicy;
|
|
296
|
+
const floorSource = this.layering?.lowerPolicySource;
|
|
297
|
+
const effective = resolveCredentialPolicy({ flag: policy, floorPolicy, floorSource, env: {} });
|
|
298
|
+
if (effective !== policy) {
|
|
299
|
+
const location = floorSource ? ` (set in ${floorSource})` : '';
|
|
300
|
+
throw new Error(`Cannot relax MCP credential policy floor "${effective}"${location} to "${policy}".`);
|
|
301
|
+
}
|
|
302
|
+
data.credentialPolicy = policy;
|
|
303
|
+
await this.save(data, true);
|
|
304
|
+
}
|
|
149
305
|
/** Clear the in-memory cache */
|
|
150
306
|
clearCache() {
|
|
151
307
|
this.cache = null;
|
|
@@ -157,27 +313,31 @@ export class McpServerRegistry {
|
|
|
157
313
|
/**
|
|
158
314
|
* Build the MCP config block for a single server in a given provider's format.
|
|
159
315
|
*/
|
|
160
|
-
function buildServerConfig(server, provider) {
|
|
316
|
+
export function buildServerConfig(server, provider) {
|
|
161
317
|
const adapter = getProviderDefinition(provider)?.adapters.mcpInjection;
|
|
318
|
+
const { env, headers } = adapter && adapter !== 'codex'
|
|
319
|
+
? renderCredentialMaps(server, adapter)
|
|
320
|
+
: { env: server.env, headers: server.headers };
|
|
162
321
|
switch (adapter) {
|
|
163
322
|
case 'antigravity': {
|
|
164
323
|
if (server.type === 'stdio') {
|
|
165
|
-
return { command: server.command, args: server.args || [], ...(
|
|
324
|
+
return { command: server.command, args: server.args || [], ...(env ? { env } : {}) };
|
|
166
325
|
}
|
|
167
|
-
return { serverUrl: server.url, ...(
|
|
326
|
+
return { serverUrl: server.url, ...(headers ? { headers } : {}) };
|
|
168
327
|
}
|
|
169
328
|
case 'claude-code': {
|
|
170
329
|
if (server.type === 'stdio') {
|
|
171
330
|
return {
|
|
172
331
|
command: server.command,
|
|
173
332
|
args: server.args || [],
|
|
174
|
-
...(
|
|
333
|
+
...(env ? { env } : {}),
|
|
175
334
|
};
|
|
176
335
|
}
|
|
177
|
-
//
|
|
336
|
+
// Claude Code skips a url entry that has no type.
|
|
178
337
|
return {
|
|
338
|
+
type: server.type,
|
|
179
339
|
url: server.url,
|
|
180
|
-
...(
|
|
340
|
+
...(headers ? { headers } : {}),
|
|
181
341
|
};
|
|
182
342
|
}
|
|
183
343
|
case 'cursor': {
|
|
@@ -185,12 +345,12 @@ function buildServerConfig(server, provider) {
|
|
|
185
345
|
return {
|
|
186
346
|
command: server.command,
|
|
187
347
|
args: server.args || [],
|
|
188
|
-
...(
|
|
348
|
+
...(env ? { env } : {}),
|
|
189
349
|
};
|
|
190
350
|
}
|
|
191
351
|
return {
|
|
192
352
|
url: server.url,
|
|
193
|
-
...(
|
|
353
|
+
...(headers ? { headers } : {}),
|
|
194
354
|
};
|
|
195
355
|
}
|
|
196
356
|
case 'factory': {
|
|
@@ -200,14 +360,14 @@ function buildServerConfig(server, provider) {
|
|
|
200
360
|
command: server.command,
|
|
201
361
|
args: server.args || [],
|
|
202
362
|
disabled: false,
|
|
203
|
-
...(
|
|
363
|
+
...(env ? { env } : {}),
|
|
204
364
|
};
|
|
205
365
|
}
|
|
206
366
|
return {
|
|
207
367
|
type: server.type,
|
|
208
368
|
url: server.url,
|
|
209
369
|
disabled: false,
|
|
210
|
-
...(
|
|
370
|
+
...(headers ? { headers } : {}),
|
|
211
371
|
};
|
|
212
372
|
}
|
|
213
373
|
case 'opencode': {
|
|
@@ -215,13 +375,14 @@ function buildServerConfig(server, provider) {
|
|
|
215
375
|
return {
|
|
216
376
|
type: 'local',
|
|
217
377
|
command: [server.command, ...(server.args || [])],
|
|
218
|
-
|
|
378
|
+
// opencode names a local server's variables `environment`.
|
|
379
|
+
...(env ? { environment: env } : {}),
|
|
219
380
|
};
|
|
220
381
|
}
|
|
221
382
|
return {
|
|
222
383
|
type: 'remote',
|
|
223
384
|
url: server.url,
|
|
224
|
-
...(
|
|
385
|
+
...(headers ? { headers } : {}),
|
|
225
386
|
};
|
|
226
387
|
}
|
|
227
388
|
case 'windsurf':
|
|
@@ -230,12 +391,12 @@ function buildServerConfig(server, provider) {
|
|
|
230
391
|
return {
|
|
231
392
|
command: server.command,
|
|
232
393
|
args: server.args || [],
|
|
233
|
-
...(
|
|
394
|
+
...(env ? { env } : {}),
|
|
234
395
|
};
|
|
235
396
|
}
|
|
236
397
|
return {
|
|
237
398
|
url: server.url,
|
|
238
|
-
...(
|
|
399
|
+
...(headers ? { headers } : {}),
|
|
239
400
|
};
|
|
240
401
|
}
|
|
241
402
|
case 'codex':
|
|
@@ -245,22 +406,13 @@ function buildServerConfig(server, provider) {
|
|
|
245
406
|
return {};
|
|
246
407
|
}
|
|
247
408
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Build a TOML section for a server (Codex/OpenAI provider).
|
|
250
|
-
*/
|
|
251
|
-
function tomlString(value) {
|
|
252
|
-
if (typeof value !== 'string' || [...value].some(char => {
|
|
253
|
-
const point = char.codePointAt(0);
|
|
254
|
-
return point >= 0xd800 && point <= 0xdfff;
|
|
255
|
-
}))
|
|
256
|
-
throw new Error('TOML values must be strings containing valid Unicode scalar values');
|
|
257
|
-
// JSON escapes align with TOML basic strings except DEL must also be escaped.
|
|
258
|
-
return JSON.stringify(value).replace(/\u007f/g, '\\u007f');
|
|
259
|
-
}
|
|
260
409
|
function tomlKey(value) {
|
|
261
410
|
return typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
|
|
262
411
|
}
|
|
263
|
-
function
|
|
412
|
+
function tomlInlineTable(values) {
|
|
413
|
+
return `{ ${Object.entries(values).map(([name, value]) => `${tomlKey(name)} = ${tomlString(value)}`).join(', ')} }`;
|
|
414
|
+
}
|
|
415
|
+
export function buildServerToml(server) {
|
|
264
416
|
const lines = [];
|
|
265
417
|
lines.push(`[mcp_servers.${tomlKey(server.name)}]`);
|
|
266
418
|
if (server.type === 'stdio') {
|
|
@@ -269,9 +421,27 @@ function buildServerToml(server) {
|
|
|
269
421
|
const argsStr = server.args.map(a => tomlString(a)).join(', ');
|
|
270
422
|
lines.push(`args = [${argsStr}]`);
|
|
271
423
|
}
|
|
424
|
+
if (server.env && Object.keys(server.env).length > 0)
|
|
425
|
+
lines.push(`env = ${tomlInlineTable(server.env)}`);
|
|
426
|
+
// Codex forwards a variable only under its own name (env_vars).
|
|
427
|
+
const forwarded = Object.entries(server.envFrom ?? {});
|
|
428
|
+
for (const [key, name] of forwarded) {
|
|
429
|
+
validateEnvReferenceName(name);
|
|
430
|
+
if (key !== name) {
|
|
431
|
+
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}.`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (forwarded.length > 0)
|
|
435
|
+
lines.push(`env_vars = [${forwarded.map(([name]) => tomlString(name)).join(', ')}]`);
|
|
272
436
|
}
|
|
273
437
|
else {
|
|
274
438
|
lines.push(`url = ${tomlString(server.url)}`);
|
|
439
|
+
if (server.headers && Object.keys(server.headers).length > 0)
|
|
440
|
+
lines.push(`http_headers = ${tomlInlineTable(server.headers)}`);
|
|
441
|
+
for (const name of Object.values(server.headerEnv ?? {}))
|
|
442
|
+
validateEnvReferenceName(name);
|
|
443
|
+
if (server.headerEnv && Object.keys(server.headerEnv).length > 0)
|
|
444
|
+
lines.push(`env_http_headers = ${tomlInlineTable(server.headerEnv)}`);
|
|
275
445
|
}
|
|
276
446
|
lines.push(`startup_timeout_sec = 10.0`);
|
|
277
447
|
lines.push(`tool_timeout_sec = 60.0`);
|
|
@@ -285,6 +455,9 @@ export function getProviderConfigPath(provider, projectDir = '.', options = {})
|
|
|
285
455
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
286
456
|
return resolve(homeDir, '.gemini/config/mcp_config.json');
|
|
287
457
|
}
|
|
458
|
+
if ((provider === 'claude-code' || provider === 'claude') && options.scope === 'user') {
|
|
459
|
+
return resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.claude.json');
|
|
460
|
+
}
|
|
288
461
|
if ((provider === 'omp' || provider === 'oh-my-pi') && options.scope !== undefined && !['user', 'project'].includes(options.scope))
|
|
289
462
|
throw new Error('OMP MCP scope must be user or project');
|
|
290
463
|
if ((provider === 'omp' || provider === 'oh-my-pi') && options.scope === 'user')
|
|
@@ -295,12 +468,12 @@ export function getProviderConfigPath(provider, projectDir = '.', options = {})
|
|
|
295
468
|
agy: resolve(projectDir, '.agents/mcp_config.json'),
|
|
296
469
|
omp: resolve(projectDir, '.omp/mcp.json'),
|
|
297
470
|
'oh-my-pi': resolve(projectDir, '.omp/mcp.json'),
|
|
298
|
-
'claude-code': resolve(projectDir, '.
|
|
299
|
-
claude: resolve(projectDir, '.
|
|
471
|
+
'claude-code': resolve(projectDir, '.mcp.json'),
|
|
472
|
+
claude: resolve(projectDir, '.mcp.json'),
|
|
300
473
|
cursor: resolve(projectDir, '.cursor/mcp.json'),
|
|
301
474
|
factory: resolve(homeDir, '.factory/mcp.json'),
|
|
302
|
-
codex: resolve(homeDir, '.codex/config.toml'),
|
|
303
|
-
openai: resolve(homeDir, '.codex/config.toml'),
|
|
475
|
+
codex: process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME, 'config.toml') : resolve(homeDir, '.codex/config.toml'),
|
|
476
|
+
openai: process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME, 'config.toml') : resolve(homeDir, '.codex/config.toml'),
|
|
304
477
|
'grok-build': options.scope === 'user'
|
|
305
478
|
? resolve(process.env.GROK_HOME || resolve(homeDir, '.grok'), 'config.toml')
|
|
306
479
|
: resolve(projectDir, '.grok/config.toml'),
|
|
@@ -327,16 +500,37 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
327
500
|
};
|
|
328
501
|
// Get servers to inject
|
|
329
502
|
let allServers = await registry.list();
|
|
330
|
-
if (serverFilter
|
|
503
|
+
if (serverFilter !== undefined) {
|
|
331
504
|
allServers = allServers.filter(s => serverFilter.includes(s.name));
|
|
332
505
|
}
|
|
506
|
+
const toolPlan = options.toolFilters
|
|
507
|
+
? planToolFilters(provider, allServers.map(server => server.name), options.toolFilters)
|
|
508
|
+
: null;
|
|
509
|
+
if (toolPlan)
|
|
510
|
+
result.warnings = toolPlan.warnings;
|
|
333
511
|
if (allServers.length === 0) {
|
|
334
512
|
result.error = 'No servers to inject. Use "aiwg mcp add" first.';
|
|
335
513
|
return result;
|
|
336
514
|
}
|
|
515
|
+
assertCredentialPolicy(allServers, resolveCredentialPolicy({
|
|
516
|
+
flag: options.credentialPolicy, registryPolicy: await registry.getCredentialPolicy(),
|
|
517
|
+
floorPolicy: await registry.getCredentialPolicyFloor(),
|
|
518
|
+
floorSource: await registry.getCredentialPolicyFloorSource(),
|
|
519
|
+
}));
|
|
520
|
+
// The shareable-project decision follows the actual resolved destination,
|
|
521
|
+
// never the requested scope: a `--scope user` request for a provider without
|
|
522
|
+
// a user-scope destination still resolves to the project file and is refused.
|
|
523
|
+
const { userScope, projectRoot } = classifyMcpDestination(provider, options.scope, configPath, projectDir);
|
|
524
|
+
await assertConfigDestination(configPath, projectRoot);
|
|
525
|
+
// Every destination inside the project directory is meant to be committed to
|
|
526
|
+
// version control; home destinations are private, so literals there are not
|
|
527
|
+
// copied into a shared file.
|
|
528
|
+
if (projectRoot) {
|
|
529
|
+
assertProjectCredentials(allServers, configPath, projectCredentialRemedy(provider, projectDir));
|
|
530
|
+
}
|
|
337
531
|
if (provider === 'omp' || provider === 'oh-my-pi') {
|
|
338
532
|
try {
|
|
339
|
-
const managed = await manageOmpMcp(configPath, allServers, { dryRun });
|
|
533
|
+
const managed = await manageOmpMcp(configPath, allServers, { dryRun, userScope, projectRoot });
|
|
340
534
|
if (!dryRun)
|
|
341
535
|
for (const server of allServers)
|
|
342
536
|
await registry.recordInjection(server.name, 'omp');
|
|
@@ -350,6 +544,7 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
350
544
|
try {
|
|
351
545
|
const managed = await manageGrokBuildMcp(configPath, allServers, {
|
|
352
546
|
dryRun,
|
|
547
|
+
userScope,
|
|
353
548
|
root: options.scope === 'user' ? dirname(dirname(configPath)) : resolve(projectDir),
|
|
354
549
|
});
|
|
355
550
|
if (!dryRun && !managed.error)
|
|
@@ -363,12 +558,12 @@ export async function injectServers(registry, provider, options = {}) {
|
|
|
363
558
|
}
|
|
364
559
|
// Handle TOML-based providers (Codex/OpenAI) separately
|
|
365
560
|
if (provider === 'codex' || provider === 'openai') {
|
|
366
|
-
return injectToml(registry, allServers, configPath, provider, dryRun, result);
|
|
561
|
+
return injectToml(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan);
|
|
367
562
|
}
|
|
368
563
|
// JSON-based providers
|
|
369
|
-
return injectJson(registry, allServers, configPath, provider, dryRun, result);
|
|
564
|
+
return injectJson(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan, { projectDir, scope: options.scope });
|
|
370
565
|
}
|
|
371
|
-
async function injectJson(registry, servers, configPath, provider, dryRun, result) {
|
|
566
|
+
async function injectJson(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null, location = {}) {
|
|
372
567
|
// Load existing config
|
|
373
568
|
let existing = {};
|
|
374
569
|
try {
|
|
@@ -406,9 +601,21 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
406
601
|
}
|
|
407
602
|
// Merge back
|
|
408
603
|
const merged = { ...existing, [mcpKey]: newServers };
|
|
604
|
+
if (toolPlan) {
|
|
605
|
+
applyJsonToolFilterPlan(merged, mcpKey, toolPlan);
|
|
606
|
+
}
|
|
607
|
+
const isClaude = provider === 'claude-code' || provider === 'claude';
|
|
608
|
+
const settingsPath = isClaude ? claudeSettingsPath(location.projectDir, location.scope) : null;
|
|
609
|
+
const settings = settingsPath ? await prepareClaudePermissions(settingsPath, toolPlan?.claudePermissions || { deny: [], allow: [] }, { userScope, projectRoot, managedDir: dirname(registry.getPath()), mcpPath: configPath }) : null;
|
|
610
|
+
if (settings?.active && settingsPath)
|
|
611
|
+
result.settingsPath = settingsPath;
|
|
612
|
+
if (settings?.warnings.length)
|
|
613
|
+
result.warnings = [...(result.warnings || []), ...settings.warnings];
|
|
409
614
|
if (!dryRun) {
|
|
410
|
-
await
|
|
411
|
-
|
|
615
|
+
await writeConfigTransaction([
|
|
616
|
+
{ file: configPath, content: JSON.stringify(merged, null, 2) + '\n', options: { userScope, projectRoot } },
|
|
617
|
+
...(settings?.writes || []),
|
|
618
|
+
]);
|
|
412
619
|
// Record injection in registry
|
|
413
620
|
for (const server of servers) {
|
|
414
621
|
if ((provider === 'antigravity' || provider === 'agy') && !result.serversInjected.includes(server.name))
|
|
@@ -418,7 +625,7 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
418
625
|
}
|
|
419
626
|
return result;
|
|
420
627
|
}
|
|
421
|
-
async function injectToml(registry, servers, configPath, provider, dryRun, result) {
|
|
628
|
+
async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
|
|
422
629
|
let existing = '';
|
|
423
630
|
try {
|
|
424
631
|
existing = await readFile(configPath, 'utf-8');
|
|
@@ -428,15 +635,15 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
|
|
|
428
635
|
throw error;
|
|
429
636
|
}
|
|
430
637
|
for (const server of servers) {
|
|
431
|
-
const
|
|
638
|
+
const filterLines = toolPlan?.tomlLines[server.name] || [];
|
|
639
|
+
const edited = replaceServer(existing, server.name, [buildServerToml(server), ...filterLines].join('\n'));
|
|
432
640
|
existing = edited.text;
|
|
433
641
|
if (edited.alreadyPresent)
|
|
434
642
|
result.alreadyPresent.push(server.name);
|
|
435
643
|
result.serversInjected.push(server.name);
|
|
436
644
|
}
|
|
437
645
|
if (!dryRun) {
|
|
438
|
-
await
|
|
439
|
-
await writeFile(configPath, existing, 'utf-8');
|
|
646
|
+
await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
|
|
440
647
|
for (const server of servers) {
|
|
441
648
|
await registry.recordInjection(server.name, provider);
|
|
442
649
|
}
|