@aiwg/cli 2026.10.1 → 2026.10.4
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/README.md +2 -2
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +25 -6
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -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/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +366 -56
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- 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 +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +127 -82
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -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 +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -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 +647 -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 +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -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.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +53 -23
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- 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 +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +30 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
- 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/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- 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 +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +360 -0
- package/tools/providers/omp-transport.mjs +194 -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/consecutive-failure-circuit-breaker.mjs +1748 -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/launcher-recovery-evidence.mjs +55 -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 +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
package/dist/src/mcp/cli.mjs
CHANGED
|
@@ -7,20 +7,146 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { startServer, createServer } from './server.mjs';
|
|
10
|
+
import { assertConfigDestination, assertConfigObject, assertProjectCredentials, isPathInsideDir, classifyMcpDestination, projectCredentialRemedy, writeConfigAtomic, writeConfigTransaction } from './config-file.mjs';
|
|
10
11
|
import fs from 'fs/promises';
|
|
11
12
|
import path from 'path';
|
|
12
|
-
import
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { existsSync as nodeExistsSync, readFileSync as nodeReadFileSync } from 'fs';
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
13
16
|
import {
|
|
14
17
|
McpServerRegistry,
|
|
18
|
+
buildServerConfig,
|
|
15
19
|
injectServers,
|
|
16
20
|
SUPPORTED_PROVIDERS,
|
|
17
21
|
getProviderConfigPath,
|
|
18
22
|
} from './registry.mjs';
|
|
19
23
|
import { McpProfileRegistry } from './profiles.mjs';
|
|
24
|
+
import {
|
|
25
|
+
applyJsonToolFilterPlan,
|
|
26
|
+
hasToolFilters,
|
|
27
|
+
planToolFilters,
|
|
28
|
+
prepareClaudePermissions,
|
|
29
|
+
resolveToolFilters,
|
|
30
|
+
} from './tool-filters.mjs';
|
|
31
|
+
import { assertCredentialPolicy, CREDENTIAL_POLICIES, installPolicyCheckedServer, redactCredentialArgs, redactCredentialText, redactCredentialRecord, resolveCredentialPolicy } from './credentials.mjs';
|
|
20
32
|
import { getMcpInjectionDefinition } from '../providers/provider-definitions.mjs';
|
|
21
33
|
import { manageOmpMcp } from './omp-config.mjs';
|
|
34
|
+
import { claudeDesktopConfigPath, resolveClaudeInstallTarget, legacyClaudeMigration } from './claude-install.mjs';
|
|
22
35
|
import { unmanageGrokBuildMcp } from './grok-build-config.mjs';
|
|
23
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when a recognized flag is present but has no usable value
|
|
39
|
+
* following it (#3062 review item 11). Mirrors `src/cli/flag-parsing.ts`'s
|
|
40
|
+
* `MissingFlagValueError`/`requireFlagValue`, duplicated in plain JS rather
|
|
41
|
+
* than imported: this file is exercised both compiled (`dist/src/mcp/
|
|
42
|
+
* cli.mjs`, where a `../cli/flag-parsing.js` sibling import would resolve)
|
|
43
|
+
* and directly from source in tests (`src/mcp/cli.mjs`, where it would
|
|
44
|
+
* not — there is no compiled `.js` sibling under `src/` itself).
|
|
45
|
+
*/
|
|
46
|
+
class MissingFlagValueError extends Error {
|
|
47
|
+
constructor(flagName) {
|
|
48
|
+
super(`${flagName} requires a value`);
|
|
49
|
+
this.name = 'MissingFlagValueError';
|
|
50
|
+
this.flagName = flagName;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Single-pass, every-occurrence-validated flag lookup (#3062 second
|
|
56
|
+
* review, finding 4 — mirrors `src/cli/flag-parsing.ts`'s
|
|
57
|
+
* `scanFlagOccurrences`/`requireFlagValue`, duplicated here for the same
|
|
58
|
+
* source/dist reason as the class above): stops at a bare `--`
|
|
59
|
+
* terminator; accepts `--flag value` and `--flag=value`; a space-
|
|
60
|
+
* separated value starting with `--` is treated as missing (use `=` for a
|
|
61
|
+
* value that must start with `--`); every occurrence is checked, not just
|
|
62
|
+
* the first; `options.rejectEmpty` additionally rejects an empty-string
|
|
63
|
+
* value. `options.precedence` picks which occurrence wins when the flag
|
|
64
|
+
* repeats — `'first'` (default) here matches this file's own pre-existing
|
|
65
|
+
* `parseFlag()` convention (first match wins).
|
|
66
|
+
*/
|
|
67
|
+
function requireFlagValue(args, options, ...names) {
|
|
68
|
+
const occurrences = [];
|
|
69
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
70
|
+
const token = args[i];
|
|
71
|
+
if (token === undefined) continue;
|
|
72
|
+
if (token === '--') break;
|
|
73
|
+
|
|
74
|
+
if (token.startsWith('--')) {
|
|
75
|
+
const eq = token.indexOf('=');
|
|
76
|
+
if (eq !== -1) {
|
|
77
|
+
const flag = token.slice(0, eq);
|
|
78
|
+
const value = token.slice(eq + 1);
|
|
79
|
+
if (names.includes(flag)) {
|
|
80
|
+
if (options.rejectEmpty && value === '') throw new MissingFlagValueError(flag);
|
|
81
|
+
occurrences.push(value);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (names.includes(token)) {
|
|
88
|
+
const next = args[i + 1];
|
|
89
|
+
if (next === undefined || next.startsWith('--')) throw new MissingFlagValueError(token);
|
|
90
|
+
if (options.rejectEmpty && next === '') throw new MissingFlagValueError(token);
|
|
91
|
+
occurrences.push(next);
|
|
92
|
+
i += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (occurrences.length === 0) return undefined;
|
|
96
|
+
return options.precedence === 'last' ? occurrences[occurrences.length - 1] : occurrences[0];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find the AIWG package root by walking up from THIS file's own location
|
|
101
|
+
* (never from `AIWG_ROOT`/`process.cwd()`, which name the *served project*,
|
|
102
|
+
* not necessarily where this module's own package lives). Depth-independent
|
|
103
|
+
* on purpose: this file ships at `src/mcp/cli.mjs` in a dev checkout and at
|
|
104
|
+
* `dist/src/mcp/cli.mjs` in the published package. Mirrors
|
|
105
|
+
* `src/mcp/tools/worker-invoke.mjs`'s own `findOwnPackageRoot` (and
|
|
106
|
+
* `tools/_resolve-impl.mjs`'s `findPackageRoot`).
|
|
107
|
+
*/
|
|
108
|
+
function findOwnPackageRoot() {
|
|
109
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
110
|
+
for (let i = 0; i < 12; i += 1) {
|
|
111
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
112
|
+
if (nodeExistsSync(pkgPath)) {
|
|
113
|
+
try {
|
|
114
|
+
const pkg = JSON.parse(nodeReadFileSync(pkgPath, 'utf8'));
|
|
115
|
+
if (pkg.name === 'aiwg' || pkg.name === '@aiwg/cli') return dir;
|
|
116
|
+
} catch { /* keep walking */ }
|
|
117
|
+
}
|
|
118
|
+
const parent = path.dirname(dir);
|
|
119
|
+
if (parent === dir) break;
|
|
120
|
+
dir = parent;
|
|
121
|
+
}
|
|
122
|
+
throw new Error('Could not locate the AIWG package root from mcp/cli.mjs');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Load the compiled `loginInProfile()` adapter (#3062 second review,
|
|
127
|
+
* finding 3). The first round routed this through `tools/_resolve-impl.mjs`,
|
|
128
|
+
* which falls back to importing the TypeScript *source* when no compiled
|
|
129
|
+
* `dist/src/` exists — but that source module's own internal imports
|
|
130
|
+
* (e.g. `../../cli/env.js`) have no `.js` sibling under `src/` either, so
|
|
131
|
+
* the fallback itself fails deep inside the adapter with an unrelated-
|
|
132
|
+
* looking `ERR_MODULE_NOT_FOUND`, on every Node version, not just ones
|
|
133
|
+
* without native TypeScript support. This loads the compiled module
|
|
134
|
+
* directly — never TypeScript source — and fails fast with one actionable
|
|
135
|
+
* line when it is missing, instead of attempting (and failing inside) a
|
|
136
|
+
* source fallback.
|
|
137
|
+
*/
|
|
138
|
+
async function loadCodexRuntimeAdapter() {
|
|
139
|
+
const root = findOwnPackageRoot();
|
|
140
|
+
const compiledPath = path.join(root, 'dist', 'src', 'mcp', 'adapters', 'codex-runtime.js');
|
|
141
|
+
if (!nodeExistsSync(compiledPath)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'aiwg mcp profile login requires the built CLI (missing dist/src/mcp/adapters/codex-runtime.js). ' +
|
|
144
|
+
'Use an installed `aiwg` for this command; a source checkout must be built first (see docs/contributing).',
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return import(pathToFileURL(compiledPath).href);
|
|
148
|
+
}
|
|
149
|
+
|
|
24
150
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
151
|
|
|
26
152
|
/**
|
|
@@ -42,6 +168,8 @@ Usage:
|
|
|
42
168
|
aiwg mcp inject [opts] Inject servers into provider configs
|
|
43
169
|
aiwg mcp uninject [opts] Remove unchanged AIWG-owned OMP server entries
|
|
44
170
|
aiwg mcp profile <sub> Manage MCP profiles (named server subsets)
|
|
171
|
+
aiwg mcp credential-policy [literal|references|none]
|
|
172
|
+
Show or set the registry's default credential policy
|
|
45
173
|
|
|
46
174
|
Server Options (for add/update):
|
|
47
175
|
--url <url> Server URL (for http/sse types)
|
|
@@ -52,6 +180,8 @@ Server Options (for add/update):
|
|
|
52
180
|
--headers <K=V,...> HTTP headers (comma-separated K=V pairs)
|
|
53
181
|
--header-env <K=ENV,...>
|
|
54
182
|
Resolve HTTP header values from environment variables
|
|
183
|
+
--env-from <K=ENV,...>
|
|
184
|
+
Resolve stdio server variables from environment variables
|
|
55
185
|
--description <text> Optional description
|
|
56
186
|
|
|
57
187
|
Inject Options:
|
|
@@ -60,16 +190,25 @@ Inject Options:
|
|
|
60
190
|
--all Inject into all previously configured providers
|
|
61
191
|
--servers <a,b,...> Only inject specific servers (comma-separated names)
|
|
62
192
|
--dry-run Show what would change without writing
|
|
193
|
+
--strict-credentials Refuse servers with literal env/headers values; render
|
|
194
|
+
only --header-env/--env-from references
|
|
195
|
+
--no-credentials Refuse servers with any credential-bearing field,
|
|
196
|
+
references included
|
|
63
197
|
|
|
64
198
|
Serve Options:
|
|
65
199
|
--transport <type> Transport type: stdio (default), http
|
|
66
200
|
--port <number> Port for HTTP transport (default: 3100)
|
|
201
|
+
--toolsets <csv> Opt-in toolsets: flows, missions, memory, kb, research,
|
|
202
|
+
activity-log, index, ralph, mc, ops, sandbox, decision, workers
|
|
203
|
+
all excludes workers; enable explicitly with --toolsets all,workers
|
|
67
204
|
|
|
68
205
|
Examples:
|
|
69
206
|
# Define MCP servers
|
|
70
207
|
aiwg mcp add fortemi --url https://memory.s9.internal/mcp --type http
|
|
71
208
|
aiwg mcp add fortemi-enterprise --url https://memory.example.internal/mcp --type http \
|
|
72
209
|
--header-env Authorization=AIWG_FORTEMI_TOKEN
|
|
210
|
+
aiwg mcp add github --type stdio --command github-mcp-server \
|
|
211
|
+
--env-from GITHUB_PERSONAL_ACCESS_TOKEN=GITHUB_TOKEN
|
|
73
212
|
aiwg mcp add gitea --url https://mcp-gitea.integrolabs.net/mcp
|
|
74
213
|
aiwg mcp add mytools --type stdio --command npx --args mcp-server-mytools
|
|
75
214
|
|
|
@@ -87,7 +226,8 @@ Examples:
|
|
|
87
226
|
aiwg mcp remove fortemi
|
|
88
227
|
|
|
89
228
|
# Install AIWG's own MCP server
|
|
90
|
-
aiwg mcp install claude
|
|
229
|
+
aiwg mcp install claude-code
|
|
230
|
+
aiwg mcp install claude-desktop
|
|
91
231
|
aiwg mcp serve
|
|
92
232
|
aiwg mcp info
|
|
93
233
|
`);
|
|
@@ -96,20 +236,24 @@ Examples:
|
|
|
96
236
|
/**
|
|
97
237
|
* Generate MCP client configuration
|
|
98
238
|
*/
|
|
99
|
-
async function generateConfig(target, projectDir = '.') {
|
|
239
|
+
async function generateConfig(target, projectDir = '.', scope = 'project') {
|
|
100
240
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
101
241
|
|
|
102
242
|
const configs = {
|
|
103
|
-
claude: {
|
|
104
|
-
path:
|
|
243
|
+
'claude-desktop': {
|
|
244
|
+
path: claudeDesktopConfigPath(),
|
|
245
|
+
content: { mcpServers: { aiwg: { command: 'aiwg', args: ['mcp', 'serve'] } } },
|
|
246
|
+
},
|
|
247
|
+
'claude-code': {
|
|
248
|
+
path: scope === 'user' ? path.join(homeDir, '.claude.json') : path.join(projectDir, '.mcp.json'),
|
|
249
|
+
userScope: scope === 'user',
|
|
105
250
|
content: {
|
|
106
251
|
mcpServers: {
|
|
107
252
|
aiwg: {
|
|
108
253
|
command: 'aiwg',
|
|
109
254
|
args: ['mcp', 'serve'],
|
|
110
|
-
env
|
|
111
|
-
AIWG_ROOT: process.env.AIWG_ROOT
|
|
112
|
-
}
|
|
255
|
+
...(scope === 'user' && process.env.AIWG_ROOT
|
|
256
|
+
? { env: { AIWG_ROOT: process.env.AIWG_ROOT } } : {})
|
|
113
257
|
}
|
|
114
258
|
}
|
|
115
259
|
}
|
|
@@ -133,6 +277,7 @@ async function generateConfig(target, projectDir = '.') {
|
|
|
133
277
|
})
|
|
134
278
|
},
|
|
135
279
|
factory: {
|
|
280
|
+
userScope: projectDir === '.' || projectDir === 'global',
|
|
136
281
|
// Factory stores MCP config at user level in ~/.factory/mcp.json
|
|
137
282
|
// or project level in .factory/mcp.json
|
|
138
283
|
path: projectDir === '.' || projectDir === 'global'
|
|
@@ -158,7 +303,8 @@ async function generateConfig(target, projectDir = '.') {
|
|
|
158
303
|
},
|
|
159
304
|
codex: {
|
|
160
305
|
// Codex stores config in ~/.codex/config.toml (TOML format)
|
|
161
|
-
path: path.join(homeDir, '.codex
|
|
306
|
+
path: path.resolve(process.env.CODEX_HOME || path.join(homeDir, '.codex'), 'config.toml'),
|
|
307
|
+
userScope: true,
|
|
162
308
|
// We generate TOML snippet to append, not JSON
|
|
163
309
|
content: null,
|
|
164
310
|
toml: `
|
|
@@ -185,8 +331,8 @@ enabled_tools = [
|
|
|
185
331
|
let existing = '';
|
|
186
332
|
try {
|
|
187
333
|
existing = await fs.readFile(configPath, 'utf-8');
|
|
188
|
-
} catch {
|
|
189
|
-
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (error.code !== 'ENOENT') throw error;
|
|
190
336
|
}
|
|
191
337
|
|
|
192
338
|
// Check if AIWG MCP already configured
|
|
@@ -198,8 +344,7 @@ enabled_tools = [
|
|
|
198
344
|
// Append TOML config
|
|
199
345
|
const updated = existing.trimEnd() + '\n' + tomlContent.trim() + '\n';
|
|
200
346
|
|
|
201
|
-
await
|
|
202
|
-
await fs.writeFile(configPath, updated);
|
|
347
|
+
await writeConfigAtomic(configPath, updated, { userScope: true });
|
|
203
348
|
console.log(`MCP configuration appended to: ${configPath}`);
|
|
204
349
|
console.log(`\nTo use AIWG MCP server with Codex:`);
|
|
205
350
|
console.log(` 1. Restart Codex CLI`);
|
|
@@ -209,12 +354,14 @@ enabled_tools = [
|
|
|
209
354
|
},
|
|
210
355
|
openai: {
|
|
211
356
|
// Alias for codex
|
|
212
|
-
path: path.join(homeDir, '.codex
|
|
357
|
+
path: path.resolve(process.env.CODEX_HOME || path.join(homeDir, '.codex'), 'config.toml'),
|
|
358
|
+
userScope: true,
|
|
213
359
|
alias: 'codex'
|
|
214
360
|
},
|
|
215
361
|
windsurf: {
|
|
216
362
|
// Windsurf stores MCP config at ~/.codeium/windsurf/mcp_config.json
|
|
217
363
|
path: path.join(homeDir, '.codeium/windsurf/mcp_config.json'),
|
|
364
|
+
userScope: true,
|
|
218
365
|
content: {
|
|
219
366
|
mcpServers: {
|
|
220
367
|
aiwg: {
|
|
@@ -312,17 +459,21 @@ enabled_tools = [
|
|
|
312
459
|
|
|
313
460
|
// Find existing config
|
|
314
461
|
for (const loc of locations) {
|
|
462
|
+
await assertConfigDestination(loc, projectRoot);
|
|
315
463
|
try {
|
|
316
464
|
const rawContent = await fs.readFile(loc, 'utf-8');
|
|
317
|
-
// Strip JSONC comments
|
|
318
|
-
const jsonContent = rawContent
|
|
319
|
-
|
|
320
|
-
.
|
|
465
|
+
// Strip JSONC comments while preserving strings such as URLs.
|
|
466
|
+
const jsonContent = rawContent.replace(
|
|
467
|
+
/"(?:\\.|[^"\\])*"|\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g,
|
|
468
|
+
match => match.startsWith('"') ? match : ' ',
|
|
469
|
+
);
|
|
321
470
|
existing = JSON.parse(jsonContent);
|
|
471
|
+
assertConfigObject(existing, 'mcp');
|
|
322
472
|
targetPath = loc;
|
|
323
473
|
break;
|
|
324
|
-
} catch {
|
|
325
|
-
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (error instanceof SyntaxError) throw new Error(`Refusing to overwrite malformed MCP config ${loc}: invalid JSON`);
|
|
476
|
+
if (error.code !== 'ENOENT') throw error;
|
|
326
477
|
}
|
|
327
478
|
}
|
|
328
479
|
|
|
@@ -335,8 +486,7 @@ enabled_tools = [
|
|
|
335
486
|
// Merge configuration
|
|
336
487
|
const merged = mergeFunc(existing, content);
|
|
337
488
|
|
|
338
|
-
await
|
|
339
|
-
await fs.writeFile(targetPath, JSON.stringify(merged, null, 2));
|
|
489
|
+
await writeConfigAtomic(targetPath, JSON.stringify(merged, null, 2), { userScope, projectRoot });
|
|
340
490
|
console.log(`MCP configuration written to: ${targetPath}`);
|
|
341
491
|
console.log(`\nTo use AIWG MCP server with OpenCode:`);
|
|
342
492
|
console.log(` 1. Restart OpenCode`);
|
|
@@ -358,21 +508,61 @@ enabled_tools = [
|
|
|
358
508
|
config = configs[config.alias];
|
|
359
509
|
}
|
|
360
510
|
|
|
511
|
+
// Destination-derived, not scope-derived (and never from a static
|
|
512
|
+
// `config.userScope` flag): a target whose resolved path still lands inside
|
|
513
|
+
// the project directory gets project containment, whatever scope was
|
|
514
|
+
// requested. Providers with no real user-scope destination (Cursor,
|
|
515
|
+
// opencode) refuse `--scope user` instead of silently writing the project
|
|
516
|
+
// file. `config.path` is null only for providers with no config file at all
|
|
517
|
+
// (Warp, UI-configured), which never reach a destination check.
|
|
518
|
+
const projectBase = target === 'opencode' && projectDir === 'global' ? process.cwd() : projectDir;
|
|
519
|
+
const { userScope, projectRoot } = config.path
|
|
520
|
+
? classifyMcpDestination(target, scope, config.path, projectBase)
|
|
521
|
+
: { userScope: Boolean(config.userScope), projectRoot: undefined };
|
|
522
|
+
if (config.path) await assertConfigDestination(config.path, projectRoot);
|
|
523
|
+
|
|
361
524
|
// Handle custom handler (for TOML configs like Codex, or OpenCode JSON)
|
|
362
525
|
if (config.handler) {
|
|
363
526
|
return await config.handler(config.path, config.toml, config.content, config.merge);
|
|
364
527
|
}
|
|
365
528
|
|
|
366
|
-
// Ensure directory exists
|
|
367
|
-
await fs.mkdir(path.dirname(config.path), { recursive: true });
|
|
368
|
-
|
|
369
529
|
// Check if file exists and merge
|
|
370
530
|
let existing = {};
|
|
371
531
|
try {
|
|
372
532
|
const content = await fs.readFile(config.path, 'utf-8');
|
|
373
533
|
existing = JSON.parse(content);
|
|
374
|
-
} catch {
|
|
375
|
-
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (error instanceof SyntaxError) throw new Error(`Refusing to overwrite malformed MCP config ${config.path}: invalid JSON`);
|
|
536
|
+
if (error.code !== 'ENOENT') throw error;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const installServerMapKey = Object.keys(config.content)[0];
|
|
540
|
+
assertConfigObject(existing, installServerMapKey);
|
|
541
|
+
|
|
542
|
+
// Install-generated server definitions go through the same resolved
|
|
543
|
+
// credential policy as `aiwg mcp inject`, before any write. AIWG_ROOT (the
|
|
544
|
+
// only literal env value any install target's fixed content can ever
|
|
545
|
+
// carry, and only on the built-in "aiwg" entry) is a named, narrow
|
|
546
|
+
// exemption - installPolicyCheckedServer/INSTALL_POLICY_EXEMPT_ENV - not
|
|
547
|
+
// an unexamined bypass: any other literal env/header is still refused.
|
|
548
|
+
const installRegistry = new McpServerRegistry();
|
|
549
|
+
assertCredentialPolicy(
|
|
550
|
+
Object.entries(config.content[installServerMapKey] || {})
|
|
551
|
+
.map(([name, server]) => installPolicyCheckedServer(name, server)),
|
|
552
|
+
resolveCredentialPolicy({
|
|
553
|
+
registryPolicy: await installRegistry.getCredentialPolicy(),
|
|
554
|
+
floorPolicy: await installRegistry.getCredentialPolicyFloor(),
|
|
555
|
+
floorSource: await installRegistry.getCredentialPolicyFloorSource(),
|
|
556
|
+
}),
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
// Only the claude target's install content can ever embed a literal credential
|
|
560
|
+
// (`AIWG_ROOT`, gated to `scope === 'user'` above, and exempted above); every
|
|
561
|
+
// other target's `configs[...]` entry above is a fixed `aiwg mcp serve`
|
|
562
|
+
// stdio/TOML stanza with no env/headers field, so there is nothing further
|
|
563
|
+
// for assertProjectCredentials to check there.
|
|
564
|
+
if (target === 'claude-code' && scope !== 'user') {
|
|
565
|
+
assertProjectCredentials(Object.entries(config.content.mcpServers).map(([name, server]) => ({ name, ...server })), config.path);
|
|
376
566
|
}
|
|
377
567
|
|
|
378
568
|
// Merge configuration using custom merge function if available
|
|
@@ -387,7 +577,13 @@ enabled_tools = [
|
|
|
387
577
|
}
|
|
388
578
|
};
|
|
389
579
|
|
|
390
|
-
|
|
580
|
+
const migration = ['claude-code', 'claude-desktop'].includes(target)
|
|
581
|
+
? await legacyClaudeMigration(projectDir) : [];
|
|
582
|
+
await writeConfigTransaction([
|
|
583
|
+
{ file: config.path, content: JSON.stringify(merged, null, 2), options: { userScope, projectRoot } },
|
|
584
|
+
...migration,
|
|
585
|
+
]);
|
|
586
|
+
if (migration.length) console.log('Removed legacy AIWG MCP entry from .claude/settings.local.json');
|
|
391
587
|
console.log(`MCP configuration written to: ${config.path}`);
|
|
392
588
|
console.log(`\nTo use AIWG MCP server with ${target}:`);
|
|
393
589
|
console.log(` 1. Restart ${target}`);
|
|
@@ -408,20 +604,22 @@ POSITIONING:
|
|
|
408
604
|
MCP is optional and provider-agnostic. Baseline AIWG reachability is file
|
|
409
605
|
deployment plus CLI discovery: aiwg discover / aiwg show <type> <name>.
|
|
410
606
|
|
|
411
|
-
CORE TOOLS (
|
|
607
|
+
CORE TOOLS (17, always registered):
|
|
412
608
|
discover Cross-type ranked catalog search
|
|
413
609
|
skill-list / skill-show Skill catalog and SKILL.md body fetch
|
|
414
610
|
command-list / command-show CLI command catalog and command spec fetch
|
|
611
|
+
rule-inventory Scoped rule activation and binding inventory
|
|
415
612
|
rule-list / rule-show Rule catalog and rule body fetch
|
|
416
613
|
agent-list / agent-show Agent catalog and agent definition fetch
|
|
417
614
|
template-list / template-show Template catalog and raw template fetch
|
|
418
615
|
template-render Render AIWG template with variables
|
|
419
616
|
command-run Allow-listed CLI dispatch; confirmation-gated when needed
|
|
420
617
|
artifact-read / artifact-write Project .aiwg/ artifact IO
|
|
618
|
+
ask-user Structured user question via elicitation or Markdown
|
|
421
619
|
|
|
422
|
-
OPT-IN TOOLSETS (
|
|
620
|
+
OPT-IN TOOLSETS (71 additional tools, including explicit workers):
|
|
423
621
|
flows flow-list / flow-show / flow-run
|
|
424
|
-
missions mission-guide / mission-dispatch / mission-status
|
|
622
|
+
missions mission-guide / mission-dispatch / mission-status / worker-resolve
|
|
425
623
|
memory memory-* and reflections-* storage operations
|
|
426
624
|
kb kb-* storage operations
|
|
427
625
|
research provenance-* and research-store-* storage operations
|
|
@@ -431,10 +629,13 @@ OPT-IN TOOLSETS (60 additional tools):
|
|
|
431
629
|
mc start / dispatch / status / stop / list
|
|
432
630
|
ops status / list / use / push
|
|
433
631
|
sandbox fleet inventory / mutation / reconciliation and governed activity
|
|
632
|
+
decision validation / evaluation / rules / features / calibration
|
|
633
|
+
workers worker-invoke (explicit opt-in; all excludes workers)
|
|
434
634
|
|
|
435
635
|
Enable opt-in tools:
|
|
436
636
|
AIWG_MCP_TOOLSETS=flows,missions,memory,kb,ralph,sandbox aiwg mcp serve
|
|
437
|
-
aiwg mcp serve --toolsets
|
|
637
|
+
aiwg mcp serve --toolsets all
|
|
638
|
+
aiwg mcp serve --toolsets all,workers # explicitly enable worker-invoke
|
|
438
639
|
|
|
439
640
|
RESOURCES:
|
|
440
641
|
aiwg://prompts/catalog List of prompt templates
|
|
@@ -455,7 +656,7 @@ TRANSPORTS:
|
|
|
455
656
|
|
|
456
657
|
ENVIRONMENT:
|
|
457
658
|
AIWG_ROOT Path to AIWG installation (default: ~/.local/share/ai-writing-guide)
|
|
458
|
-
AIWG_MCP_TOOLSETS Comma-separated opt-in toolsets;
|
|
659
|
+
AIWG_MCP_TOOLSETS Comma-separated opt-in toolsets; all excludes workers
|
|
459
660
|
AIWG_SANDBOX_MANAGEMENT_URL Sandbox management API origin (HTTPS except loopback)
|
|
460
661
|
AIWG_SANDBOX_MANAGEMENT_TOKEN_FILE Mode-0600 management bearer file for sandbox tools
|
|
461
662
|
|
|
@@ -477,6 +678,16 @@ function parseFlag(args, flag) {
|
|
|
477
678
|
return args[idx + 1];
|
|
478
679
|
}
|
|
479
680
|
|
|
681
|
+
/** Skip option values when locating a server name, regardless of flag order. */
|
|
682
|
+
function serverName(args) {
|
|
683
|
+
const valueFlags = new Set(['--type', '--url', '--command', '--args', '--env',
|
|
684
|
+
'--headers', '--header-env', '--env-from', '--description']);
|
|
685
|
+
for (let i = 0; i < args.length; i++) {
|
|
686
|
+
if (valueFlags.has(args[i])) { i++; continue; }
|
|
687
|
+
if (!args[i].startsWith('--')) return args[i];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
480
691
|
/**
|
|
481
692
|
* Parse comma-separated key=value pairs into an object
|
|
482
693
|
*/
|
|
@@ -495,8 +706,7 @@ function parseKVPairs(str) {
|
|
|
495
706
|
* Handle `aiwg mcp add <name> [opts]`
|
|
496
707
|
*/
|
|
497
708
|
async function handleAdd(args) {
|
|
498
|
-
const
|
|
499
|
-
const name = positional[0];
|
|
709
|
+
const name = serverName(args);
|
|
500
710
|
|
|
501
711
|
if (!name) {
|
|
502
712
|
console.error('Usage: aiwg mcp add <name> --url <url> [--type http|stdio|sse] [--command <cmd>] [--args <a,b>]');
|
|
@@ -510,6 +720,7 @@ async function handleAdd(args) {
|
|
|
510
720
|
const envStr = parseFlag(args, '--env');
|
|
511
721
|
const headersStr = parseFlag(args, '--headers');
|
|
512
722
|
const headerEnvStr = parseFlag(args, '--header-env');
|
|
723
|
+
const envFromStr = parseFlag(args, '--env-from');
|
|
513
724
|
const description = parseFlag(args, '--description');
|
|
514
725
|
|
|
515
726
|
if (type === 'stdio' && !command) {
|
|
@@ -531,13 +742,17 @@ async function handleAdd(args) {
|
|
|
531
742
|
env: parseKVPairs(envStr),
|
|
532
743
|
headers: parseKVPairs(headersStr),
|
|
533
744
|
headerEnv: parseKVPairs(headerEnvStr),
|
|
745
|
+
envFrom: parseKVPairs(envFromStr),
|
|
534
746
|
description,
|
|
535
747
|
});
|
|
536
748
|
|
|
537
749
|
console.log(`Added MCP server: ${name}`);
|
|
538
|
-
if (url) console.log(` URL: ${url}`);
|
|
539
|
-
if (command) console.log(` Command: ${command}`);
|
|
750
|
+
if (url) console.log(` URL: ${redactUrlUserinfo(url)}`);
|
|
751
|
+
if (command) console.log(` Command: ${redactCredentialText(command)}`);
|
|
540
752
|
console.log(` Type: ${type}`);
|
|
753
|
+
for (const [key, values] of [['env', parseKVPairs(envStr)], ['headers', parseKVPairs(headersStr)]]) {
|
|
754
|
+
if (values) console.log(` ${key}: ${Object.keys(values).join(', ')}`);
|
|
755
|
+
}
|
|
541
756
|
console.log(`\nUse "aiwg mcp inject --provider <name>" to inject into a provider config.`);
|
|
542
757
|
}
|
|
543
758
|
|
|
@@ -562,8 +777,7 @@ async function handleRemove(args) {
|
|
|
562
777
|
* Handle `aiwg mcp update <name> [opts]`
|
|
563
778
|
*/
|
|
564
779
|
async function handleUpdate(args) {
|
|
565
|
-
const
|
|
566
|
-
const name = positional[0];
|
|
780
|
+
const name = serverName(args);
|
|
567
781
|
|
|
568
782
|
if (!name) {
|
|
569
783
|
console.error('Usage: aiwg mcp update <name> --url <url> [--type <type>] ...');
|
|
@@ -578,6 +792,7 @@ async function handleUpdate(args) {
|
|
|
578
792
|
const envStr = parseFlag(args, '--env');
|
|
579
793
|
const headersStr = parseFlag(args, '--headers');
|
|
580
794
|
const headerEnvStr = parseFlag(args, '--header-env');
|
|
795
|
+
const envFromStr = parseFlag(args, '--env-from');
|
|
581
796
|
const description = parseFlag(args, '--description');
|
|
582
797
|
|
|
583
798
|
if (url !== undefined) updates.url = url;
|
|
@@ -587,6 +802,7 @@ async function handleUpdate(args) {
|
|
|
587
802
|
if (envStr !== undefined) updates.env = parseKVPairs(envStr);
|
|
588
803
|
if (headersStr !== undefined) updates.headers = parseKVPairs(headersStr);
|
|
589
804
|
if (headerEnvStr !== undefined) updates.headerEnv = parseKVPairs(headerEnvStr);
|
|
805
|
+
if (envFromStr !== undefined) updates.envFrom = parseKVPairs(envFromStr);
|
|
590
806
|
if (description !== undefined) updates.description = description;
|
|
591
807
|
|
|
592
808
|
if (Object.keys(updates).length === 0) {
|
|
@@ -598,17 +814,24 @@ async function handleUpdate(args) {
|
|
|
598
814
|
await registry.update(name, updates);
|
|
599
815
|
console.log(`Updated MCP server: ${name}`);
|
|
600
816
|
for (const [key, value] of Object.entries(updates)) {
|
|
601
|
-
|
|
817
|
+
const display = key === 'url' ? redactUrlUserinfo(value)
|
|
818
|
+
: key === 'args' ? JSON.stringify(redactCredentialArgs(value))
|
|
819
|
+
: ['env', 'headers', 'headerEnv', 'envFrom'].includes(key) ? Object.keys(value || {}).join(', ')
|
|
820
|
+
: typeof value === 'object' ? JSON.stringify(redactCredentialRecord(value)) : redactCredentialText(value);
|
|
821
|
+
console.log(` ${key}: ${display}`);
|
|
602
822
|
}
|
|
603
823
|
console.log(`\nRe-run "aiwg mcp inject --all" to propagate changes to provider configs.`);
|
|
604
824
|
}
|
|
605
825
|
|
|
826
|
+
// Refusal and all display paths share the scanner in credentials.mjs.
|
|
827
|
+
const redactUrlUserinfo = redactCredentialText;
|
|
828
|
+
|
|
606
829
|
/**
|
|
607
830
|
* Handle `aiwg mcp list` (managed servers from registry)
|
|
608
831
|
*/
|
|
609
832
|
async function handleList() {
|
|
610
833
|
const registry = new McpServerRegistry();
|
|
611
|
-
const servers = await registry.list();
|
|
834
|
+
const servers = redactCredentialRecord(await registry.list());
|
|
612
835
|
|
|
613
836
|
if (servers.length === 0) {
|
|
614
837
|
console.log('No MCP servers registered.');
|
|
@@ -621,13 +844,17 @@ async function handleList() {
|
|
|
621
844
|
for (const server of servers) {
|
|
622
845
|
console.log(` ${server.name}`);
|
|
623
846
|
console.log(` Type: ${server.type}`);
|
|
624
|
-
if (server.url) console.log(` URL: ${server.url}`);
|
|
625
|
-
if (server.command) console.log(` Command: ${server.command}${server.args ? ' ' + server.args.join(' ') : ''}`);
|
|
847
|
+
if (server.url) console.log(` URL: ${redactUrlUserinfo(server.url)}`);
|
|
848
|
+
if (server.command) console.log(` Command: ${server.command}${server.args ? ' ' + redactCredentialArgs(server.args).join(' ') : ''}`);
|
|
626
849
|
if (server.headerEnv) {
|
|
627
850
|
const refs = Object.entries(server.headerEnv).map(([header, envName]) => `${header}←${envName}`);
|
|
628
851
|
console.log(` Credential refs: ${refs.join(', ')}`);
|
|
629
852
|
}
|
|
630
|
-
if (server.
|
|
853
|
+
if (server.envFrom) {
|
|
854
|
+
const refs = Object.entries(server.envFrom).map(([key, envName]) => `${key}←${envName}`);
|
|
855
|
+
console.log(` Env refs: ${refs.join(', ')}`);
|
|
856
|
+
}
|
|
857
|
+
if (server.description) console.log(` Description: ${redactCredentialText(server.description)}`);
|
|
631
858
|
if (server.injectedProviders && server.injectedProviders.length > 0) {
|
|
632
859
|
console.log(` Injected into: ${server.injectedProviders.join(', ')}`);
|
|
633
860
|
}
|
|
@@ -660,6 +887,12 @@ async function handleInject(args) {
|
|
|
660
887
|
const profileName = parseFlag(args, '--profile');
|
|
661
888
|
const ephemeral = args.includes('--ephemeral');
|
|
662
889
|
const outPath = parseFlag(args, '--out');
|
|
890
|
+
const strictCredentials = args.includes('--strict-credentials');
|
|
891
|
+
const noCredentials = args.includes('--no-credentials');
|
|
892
|
+
if (strictCredentials && noCredentials) {
|
|
893
|
+
console.error('Error: --strict-credentials and --no-credentials are mutually exclusive.');
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
663
896
|
|
|
664
897
|
if (!provider && !injectAll) {
|
|
665
898
|
console.error('Usage: aiwg mcp inject --provider <name> [--profile <p>] [--ephemeral] [--servers a,b] [--dry-run]');
|
|
@@ -669,12 +902,19 @@ async function handleInject(args) {
|
|
|
669
902
|
}
|
|
670
903
|
|
|
671
904
|
const registry = new McpServerRegistry();
|
|
905
|
+
const credentialPolicy = resolveCredentialPolicy({
|
|
906
|
+
flag: noCredentials ? 'none' : strictCredentials ? 'references' : undefined,
|
|
907
|
+
registryPolicy: await registry.getCredentialPolicy(),
|
|
908
|
+
floorPolicy: await registry.getCredentialPolicyFloor(),
|
|
909
|
+
floorSource: await registry.getCredentialPolicyFloorSource(),
|
|
910
|
+
});
|
|
672
911
|
|
|
673
912
|
// Resolve server filter: --profile takes precedence over --servers
|
|
674
913
|
let serverFilter;
|
|
914
|
+
let profile;
|
|
675
915
|
if (profileName) {
|
|
676
916
|
const profiles = new McpProfileRegistry();
|
|
677
|
-
|
|
917
|
+
profile = await profiles.resolve(profileName);
|
|
678
918
|
if (!profile) {
|
|
679
919
|
const all = await profiles.list();
|
|
680
920
|
console.error(`Profile "${profileName}" not found.`);
|
|
@@ -707,6 +947,20 @@ async function handleInject(args) {
|
|
|
707
947
|
providers = [normalized];
|
|
708
948
|
}
|
|
709
949
|
|
|
950
|
+
if (injectAll && !ephemeral) {
|
|
951
|
+
const selected = (await registry.list()).filter(server => !serverFilter || serverFilter.includes(server.name));
|
|
952
|
+
for (const p of providers) {
|
|
953
|
+
const configPath = getProviderConfigPath(p, projectDir, { scope });
|
|
954
|
+
const { projectRoot } = classifyMcpDestination(p, scope, configPath, projectDir);
|
|
955
|
+
await assertConfigDestination(configPath, projectRoot);
|
|
956
|
+
// Every destination inside the project directory is meant to be committed
|
|
957
|
+
// to version control; home destinations are private.
|
|
958
|
+
if (projectRoot) {
|
|
959
|
+
assertProjectCredentials(selected, configPath, projectCredentialRemedy(p, projectDir));
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
710
964
|
if (ephemeral) {
|
|
711
965
|
for (const p of providers) {
|
|
712
966
|
const mcpDefinition = getMcpInjectionDefinition(p);
|
|
@@ -723,23 +977,27 @@ async function handleInject(args) {
|
|
|
723
977
|
|
|
724
978
|
let totalInjected = 0;
|
|
725
979
|
|
|
980
|
+
const printWarnings = (warnings = []) => {
|
|
981
|
+
for (const warning of warnings) console.error(` WARNING ${warning}`);
|
|
982
|
+
};
|
|
983
|
+
|
|
726
984
|
for (const p of providers) {
|
|
985
|
+
const toolFilters = profile ? resolveToolFilters(profile, p) : undefined;
|
|
727
986
|
if (ephemeral) {
|
|
728
987
|
// Generate a standalone ephemeral config file
|
|
729
|
-
const targetPath = outPath ?? path.join(
|
|
730
|
-
process.env.TMPDIR || '/tmp',
|
|
731
|
-
`aiwg-mcp-${profileName ?? 'custom'}-${p}-${Date.now()}.json`,
|
|
732
|
-
);
|
|
733
|
-
|
|
734
988
|
const allServers = await registry.list();
|
|
735
989
|
const servers = serverFilter
|
|
736
990
|
? allServers.filter(s => serverFilter.includes(s.name))
|
|
737
991
|
: allServers;
|
|
992
|
+
const toolPlan = hasToolFilters(toolFilters)
|
|
993
|
+
? planToolFilters(p, servers.map(server => server.name), toolFilters)
|
|
994
|
+
: null;
|
|
738
995
|
|
|
739
996
|
if (servers.length === 0) {
|
|
740
997
|
console.error(` ${p}: no servers to write`);
|
|
741
998
|
continue;
|
|
742
999
|
}
|
|
1000
|
+
assertCredentialPolicy(servers, credentialPolicy);
|
|
743
1001
|
|
|
744
1002
|
// Build ephemeral config in provider's format
|
|
745
1003
|
const mcpDefinition = getMcpInjectionDefinition(p);
|
|
@@ -752,32 +1010,59 @@ async function handleInject(args) {
|
|
|
752
1010
|
console.log(` Use "aiwg session --provider codex --profile ${profileName}" instead.`);
|
|
753
1011
|
continue;
|
|
754
1012
|
}
|
|
755
|
-
|
|
756
|
-
if (server.type === 'stdio') {
|
|
757
|
-
cfg.command = server.command;
|
|
758
|
-
cfg.args = server.args || [];
|
|
759
|
-
if (server.env) cfg.env = server.env;
|
|
760
|
-
} else {
|
|
761
|
-
cfg.url = server.url;
|
|
762
|
-
if (server.headers) cfg.headers = server.headers;
|
|
763
|
-
}
|
|
764
|
-
mcpBlock[server.name] = cfg;
|
|
1013
|
+
mcpBlock[server.name] = buildServerConfig(server, p);
|
|
765
1014
|
}
|
|
766
1015
|
|
|
767
1016
|
if (Object.keys(mcpBlock).length === 0) continue;
|
|
768
1017
|
|
|
769
1018
|
const config = { [mcpKey]: mcpBlock };
|
|
1019
|
+
if (toolPlan) applyJsonToolFilterPlan(config, mcpKey, toolPlan);
|
|
1020
|
+
const tempDir = outPath || dryRun
|
|
1021
|
+
? undefined
|
|
1022
|
+
: await fs.mkdtemp(path.join(os.tmpdir(), 'aiwg-mcp-'));
|
|
1023
|
+
const targetPath = outPath ?? path.join(
|
|
1024
|
+
tempDir ?? path.join(os.tmpdir(), 'aiwg-mcp-<random>'),
|
|
1025
|
+
`${profileName ?? 'custom'}-${p}.json`,
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
// Claude Code reads permission rules from settings, not from --mcp-config.
|
|
1029
|
+
const settingsPath = toolPlan?.claudePermissions ? targetPath.replace(/(\.json)?$/, '.settings.json') : null;
|
|
1030
|
+
// Explicit --out paths may be outside the project. Check every parent from
|
|
1031
|
+
// the filesystem root so neither destination can traverse a symlink.
|
|
1032
|
+
const outputRoot = path.parse(path.resolve(targetPath)).root;
|
|
1033
|
+
await assertConfigDestination(targetPath, outputRoot);
|
|
1034
|
+
const realProject = await fs.realpath(path.resolve(projectDir)).catch(error => {
|
|
1035
|
+
if (error.code !== 'ENOENT') throw error;
|
|
1036
|
+
return path.resolve(projectDir);
|
|
1037
|
+
});
|
|
1038
|
+
if (isPathInsideDir(targetPath, projectDir) || isPathInsideDir(targetPath, realProject)) {
|
|
1039
|
+
assertProjectCredentials(servers, targetPath, projectCredentialRemedy(p, projectDir));
|
|
1040
|
+
}
|
|
1041
|
+
const preparedPermissions = settingsPath
|
|
1042
|
+
? await prepareClaudePermissions(settingsPath, toolPlan.claudePermissions, {
|
|
1043
|
+
projectRoot: outputRoot,
|
|
1044
|
+
userScope: true,
|
|
1045
|
+
managedDir: path.dirname(registry.getPath()),
|
|
1046
|
+
sidecar: true,
|
|
1047
|
+
mcpPath: targetPath,
|
|
1048
|
+
})
|
|
1049
|
+
: null;
|
|
770
1050
|
|
|
771
1051
|
if (!dryRun) {
|
|
772
|
-
await
|
|
773
|
-
|
|
1052
|
+
await writeConfigTransaction([
|
|
1053
|
+
{ file: targetPath, content: JSON.stringify(config, null, 2) + '\n',
|
|
1054
|
+
options: { userScope: true, projectRoot: outputRoot } },
|
|
1055
|
+
...(preparedPermissions?.writes || []),
|
|
1056
|
+
]);
|
|
774
1057
|
}
|
|
775
1058
|
|
|
776
1059
|
const prefix = dryRun ? '[DRY RUN] ' : '';
|
|
777
1060
|
console.log(`${prefix}${p}: ${targetPath}`);
|
|
778
1061
|
console.log(` ${prefix}Servers: ${Object.keys(mcpBlock).join(', ')}`);
|
|
1062
|
+
if (settingsPath) console.log(` ${prefix}Tool permissions: ${settingsPath}`);
|
|
1063
|
+
printWarnings([...(toolPlan?.warnings || []), ...(preparedPermissions?.warnings || [])]);
|
|
779
1064
|
if (!dryRun && (p === 'claude-code' || p === 'claude')) {
|
|
780
|
-
console.log(` Launch with: claude --mcp-config ${targetPath}`);
|
|
1065
|
+
console.log(` Launch with: claude --mcp-config ${targetPath}${settingsPath ? ` --settings ${settingsPath}` : ''}`);
|
|
781
1066
|
}
|
|
782
1067
|
totalInjected += Object.keys(mcpBlock).length;
|
|
783
1068
|
continue;
|
|
@@ -789,6 +1074,8 @@ async function handleInject(args) {
|
|
|
789
1074
|
projectDir,
|
|
790
1075
|
dryRun,
|
|
791
1076
|
scope,
|
|
1077
|
+
...(hasToolFilters(toolFilters) ? { toolFilters } : {}),
|
|
1078
|
+
credentialPolicy,
|
|
792
1079
|
});
|
|
793
1080
|
|
|
794
1081
|
if (result.error) {
|
|
@@ -806,6 +1093,8 @@ async function handleInject(args) {
|
|
|
806
1093
|
if (result.alreadyPresent.length > 0) {
|
|
807
1094
|
console.log(` ${prefix}Updated in place: ${result.alreadyPresent.join(', ')}`);
|
|
808
1095
|
}
|
|
1096
|
+
if (result.settingsPath) console.log(` ${prefix}Tool permissions: ${result.settingsPath}`);
|
|
1097
|
+
printWarnings(result.warnings);
|
|
809
1098
|
}
|
|
810
1099
|
|
|
811
1100
|
if (!dryRun && totalInjected > 0 && !ephemeral) {
|
|
@@ -825,14 +1114,18 @@ function printProfileUsage() {
|
|
|
825
1114
|
aiwg mcp profile — MCP server profiles (named server subsets)
|
|
826
1115
|
|
|
827
1116
|
Usage:
|
|
828
|
-
aiwg mcp profile add <name> --servers a,b,c [--description "..."]
|
|
1117
|
+
aiwg mcp profile add <name> --servers a,b,c [--extends base,...] [--description "..."]
|
|
1118
|
+
[--provider <p|*>] [--tool-deny s__t,...] [--tool-allow s__t,...]
|
|
829
1119
|
aiwg mcp profile list
|
|
830
|
-
aiwg mcp profile show <name>
|
|
1120
|
+
aiwg mcp profile show <name> [--json]
|
|
831
1121
|
aiwg mcp profile edit <name> [--add-server x] [--remove-server y] [--description "..."]
|
|
1122
|
+
[--provider <p|*>] [--tool-deny s__t,...] [--tool-allow s__t,...]
|
|
1123
|
+
[--clear-tool-filters]
|
|
832
1124
|
aiwg mcp profile remove <name>
|
|
833
1125
|
aiwg mcp profile import <file>
|
|
834
1126
|
aiwg mcp profile export <name> [--out <file>]
|
|
835
1127
|
aiwg mcp profile init-presets
|
|
1128
|
+
aiwg mcp profile login <name> [--api-key-stdin | --api-key-file <path> | --api-key-env <ENV_VAR_NAME>]
|
|
836
1129
|
|
|
837
1130
|
Profiles let you define named subsets of your registered MCP servers:
|
|
838
1131
|
aiwg mcp profile add dev --servers git-gitea,memory-fortemi --description "Dev work"
|
|
@@ -840,11 +1133,60 @@ Profiles let you define named subsets of your registered MCP servers:
|
|
|
840
1133
|
aiwg mcp inject --provider claude --profile dev --ephemeral
|
|
841
1134
|
aiwg session --provider claude --profile dev
|
|
842
1135
|
|
|
1136
|
+
Layered configuration (organisation base, identity overlay):
|
|
1137
|
+
AIWG_CONFIG_LAYERS=/etc/aiwg/org:/home/me/.aiwg/acme aiwg mcp inject \
|
|
1138
|
+
--provider claude --profile acme-dev --ephemeral --out /tmp/acme.json
|
|
1139
|
+
Directories are listed lowest precedence first (path delimiter separated).
|
|
1140
|
+
A server or profile in a later layer replaces one of the same name in an
|
|
1141
|
+
earlier layer. Writes go to the last layer. --extends lets a profile inherit
|
|
1142
|
+
another profile's servers and tool filters from any layer.
|
|
1143
|
+
|
|
1144
|
+
Tool filters name tools as <server>__<tool>; <tool> may contain *. --provider
|
|
1145
|
+
defaults to *, which applies to every provider. inject renders them into each
|
|
1146
|
+
provider's own setting and warns about any filter the provider cannot express:
|
|
1147
|
+
aiwg mcp profile edit dev --tool-deny git-gitea__delete_repo
|
|
1148
|
+
aiwg mcp profile edit dev --provider codex --tool-allow git-gitea__list_repos
|
|
1149
|
+
|
|
843
1150
|
Preset profiles (minimal, dev, ops, research, incident, full):
|
|
844
1151
|
aiwg mcp profile init-presets
|
|
1152
|
+
|
|
1153
|
+
Logging in to a Codex profile's isolated runtime home (#3062):
|
|
1154
|
+
aiwg mcp profile login ops interactive (requires a terminal)
|
|
1155
|
+
vault read -field=key secret/openai |
|
|
1156
|
+
aiwg mcp profile login ops --api-key-stdin headless, most preferred — key never
|
|
1157
|
+
touches an environment variable or argv
|
|
1158
|
+
aiwg mcp profile login ops --api-key-file ~/.openai-key
|
|
1159
|
+
headless — file must be owned by you,
|
|
1160
|
+
mode 600, not a symlink
|
|
1161
|
+
aiwg mcp profile login ops --api-key-env OPENAI_KEY
|
|
1162
|
+
headless, least preferred — reads
|
|
1163
|
+
$OPENAI_KEY; visible to every other
|
|
1164
|
+
child process this shell starts
|
|
1165
|
+
In every case the key reaches codex over stdin only; it never appears on
|
|
1166
|
+
argv, in logs, or in error messages. Exactly one of the three may be
|
|
1167
|
+
given. Without a terminal and without any of the three, this errors
|
|
1168
|
+
naming all three. See "Supplying credentials safely" in the docs:
|
|
1169
|
+
docs/mcp/codex-profiles.md#supplying-credentials-safely
|
|
845
1170
|
`);
|
|
846
1171
|
}
|
|
847
1172
|
|
|
1173
|
+
/**
|
|
1174
|
+
* Parse --provider / --tool-deny / --tool-allow into a providerOverrides map.
|
|
1175
|
+
*/
|
|
1176
|
+
function parseToolFilterFlags(args) {
|
|
1177
|
+
const deny = parseFlag(args, '--tool-deny');
|
|
1178
|
+
const allow = parseFlag(args, '--tool-allow');
|
|
1179
|
+
if (!deny && !allow) return undefined;
|
|
1180
|
+
const split = value => value.split(',').map(s => s.trim()).filter(Boolean);
|
|
1181
|
+
const provider = parseFlag(args, '--provider') || '*';
|
|
1182
|
+
return {
|
|
1183
|
+
[provider]: {
|
|
1184
|
+
...(deny ? { toolDeny: split(deny) } : {}),
|
|
1185
|
+
...(allow ? { toolAllow: split(allow) } : {}),
|
|
1186
|
+
},
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
848
1190
|
/**
|
|
849
1191
|
* Handle `aiwg mcp profile add <name> [opts]`
|
|
850
1192
|
*/
|
|
@@ -858,17 +1200,26 @@ async function handleProfileAdd(args) {
|
|
|
858
1200
|
|
|
859
1201
|
const serversStr = parseFlag(args, '--servers');
|
|
860
1202
|
const description = parseFlag(args, '--description');
|
|
1203
|
+
const extendsStr = parseFlag(args, '--extends');
|
|
1204
|
+
const bases = extendsStr ? extendsStr.split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
861
1205
|
|
|
862
|
-
if (!serversStr && name !== 'minimal') {
|
|
1206
|
+
if (!serversStr && !bases && name !== 'minimal') {
|
|
863
1207
|
console.error('Warning: no --servers specified. Profile will start empty.');
|
|
864
1208
|
}
|
|
865
1209
|
|
|
866
1210
|
const servers = serversStr ? serversStr.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
1211
|
+
const providerOverrides = parseToolFilterFlags(args);
|
|
867
1212
|
|
|
868
1213
|
const profiles = new McpProfileRegistry();
|
|
869
1214
|
const registry = new McpServerRegistry();
|
|
870
1215
|
|
|
871
|
-
await profiles.add({
|
|
1216
|
+
await profiles.add({
|
|
1217
|
+
name,
|
|
1218
|
+
description,
|
|
1219
|
+
servers,
|
|
1220
|
+
...(bases ? { extends: bases } : {}),
|
|
1221
|
+
...(providerOverrides ? { providerOverrides } : {}),
|
|
1222
|
+
}, registry);
|
|
872
1223
|
|
|
873
1224
|
console.log(`Profile added: ${name}`);
|
|
874
1225
|
if (description) console.log(` Description: ${description}`);
|
|
@@ -881,7 +1232,7 @@ async function handleProfileAdd(args) {
|
|
|
881
1232
|
*/
|
|
882
1233
|
async function handleProfileList() {
|
|
883
1234
|
const profiles = new McpProfileRegistry();
|
|
884
|
-
const all = await profiles.list();
|
|
1235
|
+
const all = redactCredentialRecord(await profiles.list());
|
|
885
1236
|
|
|
886
1237
|
if (all.length === 0) {
|
|
887
1238
|
console.log('No profiles defined.');
|
|
@@ -911,7 +1262,7 @@ async function handleProfileShow(args) {
|
|
|
911
1262
|
}
|
|
912
1263
|
|
|
913
1264
|
const profiles = new McpProfileRegistry();
|
|
914
|
-
const profile = await profiles.get(name);
|
|
1265
|
+
const profile = redactCredentialRecord(await profiles.get(name));
|
|
915
1266
|
|
|
916
1267
|
if (!profile) {
|
|
917
1268
|
const all = await profiles.list();
|
|
@@ -922,8 +1273,18 @@ async function handleProfileShow(args) {
|
|
|
922
1273
|
process.exit(1);
|
|
923
1274
|
}
|
|
924
1275
|
|
|
1276
|
+
if (args.includes('--json')) {
|
|
1277
|
+
console.log(JSON.stringify(redactCredentialRecord(profile), null, 2));
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
925
1281
|
console.log(`Profile: ${profile.name}`);
|
|
926
1282
|
if (profile.description) console.log(`Description: ${profile.description}`);
|
|
1283
|
+
if (profile.extends?.length) {
|
|
1284
|
+
console.log(`Extends: ${profile.extends.join(', ')}`);
|
|
1285
|
+
const resolved = await profiles.resolve(name);
|
|
1286
|
+
console.log(`Resolved servers: ${resolved.servers.join(', ') || '(none)'}`);
|
|
1287
|
+
}
|
|
927
1288
|
console.log(`\nServers (${profile.servers.length}):`);
|
|
928
1289
|
|
|
929
1290
|
if (profile.servers.length === 0) {
|
|
@@ -934,11 +1295,11 @@ async function handleProfileShow(args) {
|
|
|
934
1295
|
// Resolve server configs
|
|
935
1296
|
const registry = new McpServerRegistry();
|
|
936
1297
|
for (const serverName of profile.servers) {
|
|
937
|
-
const server = await registry.get(serverName);
|
|
1298
|
+
const server = redactCredentialRecord(await registry.get(serverName));
|
|
938
1299
|
if (server) {
|
|
939
1300
|
const detail = server.type === 'stdio'
|
|
940
|
-
? `stdio ${server.command}${server.args ? ' ' + server.args.join(' ') : ''}`
|
|
941
|
-
: `${server.type} ${server.url}`;
|
|
1301
|
+
? `stdio ${server.command}${server.args ? ' ' + redactCredentialArgs(server.args).join(' ') : ''}`
|
|
1302
|
+
: `${server.type} ${redactUrlUserinfo(server.url)}`;
|
|
942
1303
|
console.log(` ${serverName.padEnd(24)} ${detail}`);
|
|
943
1304
|
if (server.description) console.log(` ${''.padEnd(24)} ${server.description}`);
|
|
944
1305
|
} else {
|
|
@@ -975,14 +1336,18 @@ async function handleProfileEdit(args) {
|
|
|
975
1336
|
const removeServer = parseFlag(args, '--remove-server');
|
|
976
1337
|
const description = parseFlag(args, '--description');
|
|
977
1338
|
|
|
1339
|
+
const providerOverrides = parseToolFilterFlags(args);
|
|
1340
|
+
const clearToolFilters = args.includes('--clear-tool-filters');
|
|
978
1341
|
const changes = {
|
|
979
1342
|
description,
|
|
980
1343
|
addServers: addServer ? addServer.split(',').map(s => s.trim()) : undefined,
|
|
981
1344
|
removeServers: removeServer ? removeServer.split(',').map(s => s.trim()) : undefined,
|
|
1345
|
+
providerOverrides,
|
|
1346
|
+
clearToolFilters: clearToolFilters ? (parseFlag(args, '--provider') || '*') : undefined,
|
|
982
1347
|
};
|
|
983
1348
|
|
|
984
|
-
if (!description && !addServer && !removeServer) {
|
|
985
|
-
console.error('No changes specified. Use --add-server, --remove-server, or --
|
|
1349
|
+
if (!description && !addServer && !removeServer && !providerOverrides && !clearToolFilters) {
|
|
1350
|
+
console.error('No changes specified. Use --add-server, --remove-server, --description, --tool-deny, --tool-allow or --clear-tool-filters.');
|
|
986
1351
|
process.exit(1);
|
|
987
1352
|
}
|
|
988
1353
|
|
|
@@ -1055,6 +1420,135 @@ async function handleProfileInitPresets() {
|
|
|
1055
1420
|
}
|
|
1056
1421
|
}
|
|
1057
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* Read all of stdin and return it as one trimmed string (go/no-go review,
|
|
1425
|
+
* blocker 1C's `--api-key-stdin`). Mirrors `src/cli/handlers/decision.ts`'s
|
|
1426
|
+
* own private `readStdin()` + `.trim()` for `--token-stdin` — duplicated
|
|
1427
|
+
* rather than imported for the same source/dist reason documented on
|
|
1428
|
+
* `requireFlagValue` above (this file runs both compiled and directly from
|
|
1429
|
+
* source). Input is expected to be a single line; a value containing
|
|
1430
|
+
* additional lines is not specially rejected, matching that precedent.
|
|
1431
|
+
*/
|
|
1432
|
+
async function readStdinAll() {
|
|
1433
|
+
const chunks = [];
|
|
1434
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
1435
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Handle `aiwg mcp profile login <name> [--api-key-env <ENV_VAR_NAME> |
|
|
1440
|
+
* --api-key-stdin | --api-key-file <path>]` (#3062, #3069, go/no-go review
|
|
1441
|
+
* blocker 1C).
|
|
1442
|
+
*
|
|
1443
|
+
* Wires `loginInProfile()` (src/mcp/adapters/codex-runtime.ts), which
|
|
1444
|
+
* already implements the full headless-safe behavior this command needs:
|
|
1445
|
+
* with a terminal attached and none of the three given, it runs the
|
|
1446
|
+
* interactive `codex login` OAuth flow. Exactly one credential source may
|
|
1447
|
+
* be given; more than one is a usage error naming the flags given. In
|
|
1448
|
+
* every case the key reaches `loginInProfile()` only as an in-memory value
|
|
1449
|
+
* (or, for `--api-key-env`, a variable *name*) — never on argv, in logs, or
|
|
1450
|
+
* in an error message — and travels to `codex` only on its stdin (see
|
|
1451
|
+
* `spawnCodexLoginWithApiKey()`'s own doc comment). Without a terminal and
|
|
1452
|
+
* without any of the three, `loginInProfile()` itself refuses, naming all
|
|
1453
|
+
* three flags.
|
|
1454
|
+
*
|
|
1455
|
+
* `--api-key-env` reads the key from that named environment variable, as
|
|
1456
|
+
* before. `--api-key-stdin` reads one line from this process's own stdin
|
|
1457
|
+
* (refusing if stdin is a terminal, or if it is empty) — the least
|
|
1458
|
+
* observable source, since the key then never exists in any environment or
|
|
1459
|
+
* on any command line. `--api-key-file <path>` reads the key from a file
|
|
1460
|
+
* that `readApiKeyFromFile()` requires to be a non-symlink, single-hard-
|
|
1461
|
+
* link, regular file owned by the current user with no group/other
|
|
1462
|
+
* permission bits on POSIX — refused otherwise, without ever printing the
|
|
1463
|
+
* path's contents. `--api-key-env` remains the least preferred of the
|
|
1464
|
+
* three (see "Supplying credentials safely" in the docs): the named
|
|
1465
|
+
* variable is visible to every other child process this shell starts.
|
|
1466
|
+
*/
|
|
1467
|
+
async function handleProfileLogin(args) {
|
|
1468
|
+
let apiKeyEnv;
|
|
1469
|
+
let apiKeyFilePath;
|
|
1470
|
+
try {
|
|
1471
|
+
// rejectEmpty: an explicitly-given-but-empty variable name/path can
|
|
1472
|
+
// never name a real environment variable or file (#3062 second review,
|
|
1473
|
+
// finding 4; go/no-go review blocker 1C).
|
|
1474
|
+
apiKeyEnv = requireFlagValue(args, { rejectEmpty: true }, '--api-key-env');
|
|
1475
|
+
apiKeyFilePath = requireFlagValue(args, { rejectEmpty: true }, '--api-key-file');
|
|
1476
|
+
} catch (err) {
|
|
1477
|
+
if (err instanceof MissingFlagValueError) {
|
|
1478
|
+
console.error(`${err.message}.`);
|
|
1479
|
+
process.exit(2);
|
|
1480
|
+
}
|
|
1481
|
+
throw err;
|
|
1482
|
+
}
|
|
1483
|
+
const useStdin = args.includes('--api-key-stdin');
|
|
1484
|
+
|
|
1485
|
+
const sourcesGiven = [
|
|
1486
|
+
apiKeyEnv !== undefined ? '--api-key-env' : null,
|
|
1487
|
+
useStdin ? '--api-key-stdin' : null,
|
|
1488
|
+
apiKeyFilePath !== undefined ? '--api-key-file' : null,
|
|
1489
|
+
].filter(Boolean);
|
|
1490
|
+
if (sourcesGiven.length > 1) {
|
|
1491
|
+
console.error(`Only one credential source may be given; got ${sourcesGiven.join(', ')}.`);
|
|
1492
|
+
process.exit(2);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// Skip --api-key-env's/--api-key-file's own value when hunting for the
|
|
1496
|
+
// positional profile name, so e.g. `login --api-key-env FOO` (missing
|
|
1497
|
+
// name) never misreads FOO as the name (mirrors this file's own
|
|
1498
|
+
// `serverName()` helper). Stops at a bare `--` the same way
|
|
1499
|
+
// requireFlagValue() does.
|
|
1500
|
+
const positional = [];
|
|
1501
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1502
|
+
const token = args[i];
|
|
1503
|
+
if (token === '--') break;
|
|
1504
|
+
if (token === '--api-key-env' || token === '--api-key-file') { i += 1; continue; }
|
|
1505
|
+
if (!token.startsWith('--')) positional.push(token);
|
|
1506
|
+
}
|
|
1507
|
+
const name = positional[0];
|
|
1508
|
+
if (!name) {
|
|
1509
|
+
console.error('Usage: aiwg mcp profile login <name> [--api-key-env <ENV_VAR_NAME> | --api-key-stdin | --api-key-file <path>]');
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
let apiKey;
|
|
1514
|
+
if (useStdin) {
|
|
1515
|
+
if (process.stdin.isTTY) {
|
|
1516
|
+
console.error(
|
|
1517
|
+
'--api-key-stdin requires piped input, not a terminal; run ' +
|
|
1518
|
+
`\`aiwg mcp profile login ${name}\` with no credential flag for the interactive login instead.`,
|
|
1519
|
+
);
|
|
1520
|
+
process.exit(2);
|
|
1521
|
+
}
|
|
1522
|
+
apiKey = (await readStdinAll()).trim();
|
|
1523
|
+
if (!apiKey) {
|
|
1524
|
+
console.error('--api-key-stdin received no key (stdin was empty).');
|
|
1525
|
+
process.exit(2);
|
|
1526
|
+
}
|
|
1527
|
+
} else if (apiKeyFilePath !== undefined) {
|
|
1528
|
+
const { readApiKeyFromFile } = await loadCodexRuntimeAdapter();
|
|
1529
|
+
try {
|
|
1530
|
+
apiKey = await readApiKeyFromFile(apiKeyFilePath);
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1533
|
+
process.exit(2);
|
|
1534
|
+
}
|
|
1535
|
+
if (!apiKey) {
|
|
1536
|
+
console.error(`--api-key-file "${apiKeyFilePath}" is empty.`);
|
|
1537
|
+
process.exit(2);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
try {
|
|
1542
|
+
const { loginInProfile } = await loadCodexRuntimeAdapter();
|
|
1543
|
+
const loginOptions = apiKey !== undefined ? { apiKey } : apiKeyEnv ? { apiKeyEnv } : {};
|
|
1544
|
+
await loginInProfile(name, loginOptions);
|
|
1545
|
+
console.log(`\nLogged in to profile "${name}".`);
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1548
|
+
process.exit(1);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1058
1552
|
/**
|
|
1059
1553
|
* Route `aiwg mcp profile <subcommand>`
|
|
1060
1554
|
*/
|
|
@@ -1080,6 +1574,9 @@ async function handleProfile(args) {
|
|
|
1080
1574
|
case 'rm':
|
|
1081
1575
|
await handleProfileRemove(subArgs);
|
|
1082
1576
|
break;
|
|
1577
|
+
case 'login':
|
|
1578
|
+
await handleProfileLogin(subArgs);
|
|
1579
|
+
break;
|
|
1083
1580
|
case 'import':
|
|
1084
1581
|
await handleProfileImport(subArgs);
|
|
1085
1582
|
break;
|
|
@@ -1137,14 +1634,25 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1137
1634
|
if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
|
|
1138
1635
|
const projectDir = parseFlag(args, '--project') || (args[2] && !args[2].startsWith('--') ? args[2] : '.');
|
|
1139
1636
|
const configPath = getProviderConfigPath('omp', projectDir, { scope });
|
|
1140
|
-
|
|
1637
|
+
// Destination-derived, not scope-derived: a provider home-directory
|
|
1638
|
+
// override (e.g. PI_CODING_AGENT_DIR) that still resolves inside the
|
|
1639
|
+
// project must get project containment, whatever scope was requested.
|
|
1640
|
+
const { userScope, projectRoot } = classifyMcpDestination('omp', scope, configPath, projectDir);
|
|
1641
|
+
await assertConfigDestination(configPath, projectRoot);
|
|
1642
|
+
const result = await manageOmpMcp(configPath, [{ name: 'aiwg', type: 'stdio', command: 'aiwg', args: ['mcp', 'serve'] }], { dryRun: args.includes('--dry-run'), userScope, projectRoot });
|
|
1141
1643
|
console.log(JSON.stringify(result, null, 2));
|
|
1142
1644
|
break;
|
|
1143
1645
|
}
|
|
1144
1646
|
// Parse install arguments (skip flags)
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
const
|
|
1647
|
+
const scope = parseFlag(args, '--scope') || 'project';
|
|
1648
|
+
if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
|
|
1649
|
+
const installArgs = args.slice(1).filter((a, index, rest) => !a.startsWith('--') && !['--scope', '--project'].includes(rest[index - 1]));
|
|
1650
|
+
const requestedTarget = installArgs[0] || 'claude';
|
|
1651
|
+
const target = resolveClaudeInstallTarget(requestedTarget, scope);
|
|
1652
|
+
if (requestedTarget === 'claude') {
|
|
1653
|
+
console.warn(`Deprecated MCP install target "claude" selected ${target}; use "aiwg mcp install ${target}" explicitly.`);
|
|
1654
|
+
}
|
|
1655
|
+
const projectDir = parseFlag(args, '--project') || installArgs[1] || '.';
|
|
1148
1656
|
|
|
1149
1657
|
// Check for --dry-run flag
|
|
1150
1658
|
if (args.includes('--dry-run')) {
|
|
@@ -1152,13 +1660,14 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1152
1660
|
console.log(`[DRY RUN] Would generate MCP config for: ${target}`);
|
|
1153
1661
|
console.log(`[DRY RUN] Target directory: ${projectDir}`);
|
|
1154
1662
|
const configPaths = {
|
|
1155
|
-
claude:
|
|
1663
|
+
'claude-desktop': claudeDesktopConfigPath(),
|
|
1664
|
+
'claude-code': scope === 'user' ? path.join(homeDir, '.claude.json') : path.join(projectDir, '.mcp.json'),
|
|
1156
1665
|
cursor: '.cursor/mcp.json',
|
|
1157
1666
|
factory: (projectDir === '.' || projectDir === 'global')
|
|
1158
1667
|
? path.join(homeDir, '.factory/mcp.json')
|
|
1159
1668
|
: path.join(projectDir, '.factory/mcp.json'),
|
|
1160
|
-
codex: path.join(homeDir, '.codex
|
|
1161
|
-
openai: path.join(homeDir, '.codex
|
|
1669
|
+
codex: path.resolve(process.env.CODEX_HOME || path.join(homeDir, '.codex'), 'config.toml'),
|
|
1670
|
+
openai: path.resolve(process.env.CODEX_HOME || path.join(homeDir, '.codex'), 'config.toml'),
|
|
1162
1671
|
vscode: '.vscode/mcp.json',
|
|
1163
1672
|
copilot: '.vscode/mcp.json',
|
|
1164
1673
|
opencode: (projectDir === '.' || projectDir === 'global')
|
|
@@ -1171,7 +1680,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1171
1680
|
break;
|
|
1172
1681
|
}
|
|
1173
1682
|
|
|
1174
|
-
await generateConfig(target, projectDir);
|
|
1683
|
+
if (!await generateConfig(target, projectDir, scope)) process.exitCode = 1;
|
|
1175
1684
|
break;
|
|
1176
1685
|
}
|
|
1177
1686
|
|
|
@@ -1208,9 +1717,9 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1208
1717
|
if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
|
|
1209
1718
|
const remove = (parseFlag(subArgs, '--servers') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
1210
1719
|
if (!remove.length) throw new Error('uninject requires --servers name[,name]');
|
|
1211
|
-
const
|
|
1720
|
+
const projectDir = parseFlag(subArgs, '--project') || '.';
|
|
1721
|
+
const configPath = getProviderConfigPath(provider, projectDir, { scope });
|
|
1212
1722
|
if (provider === 'grok-build') {
|
|
1213
|
-
const projectDir = parseFlag(subArgs, '--project') || '.';
|
|
1214
1723
|
const result = await unmanageGrokBuildMcp(configPath, remove, {
|
|
1215
1724
|
dryRun: subArgs.includes('--dry-run'),
|
|
1216
1725
|
root: scope === 'user' ? path.dirname(path.dirname(configPath)) : path.resolve(projectDir),
|
|
@@ -1218,7 +1727,17 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1218
1727
|
console.log(JSON.stringify(result, null, 2));
|
|
1219
1728
|
break;
|
|
1220
1729
|
}
|
|
1221
|
-
|
|
1730
|
+
// Destination-derived, not scope-derived: a provider home-directory
|
|
1731
|
+
// override that still resolves inside the project must get project
|
|
1732
|
+
// containment, whatever scope was requested.
|
|
1733
|
+
const { userScope: ompUserScope, projectRoot: ompProjectRoot } =
|
|
1734
|
+
classifyMcpDestination(provider, scope, configPath, projectDir);
|
|
1735
|
+
const result = await manageOmpMcp(configPath, [], {
|
|
1736
|
+
remove,
|
|
1737
|
+
dryRun: subArgs.includes('--dry-run'),
|
|
1738
|
+
userScope: ompUserScope,
|
|
1739
|
+
projectRoot: ompProjectRoot,
|
|
1740
|
+
});
|
|
1222
1741
|
console.log(JSON.stringify(result, null, 2));
|
|
1223
1742
|
break;
|
|
1224
1743
|
}
|
|
@@ -1227,6 +1746,22 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1227
1746
|
await handleProfile(subArgs);
|
|
1228
1747
|
break;
|
|
1229
1748
|
|
|
1749
|
+
case 'credential-policy': {
|
|
1750
|
+
const registry = new McpServerRegistry();
|
|
1751
|
+
const value = subArgs.find(a => !a.startsWith('--'));
|
|
1752
|
+
if (!value) {
|
|
1753
|
+
console.log(await registry.getCredentialPolicy() || 'literal');
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1756
|
+
if (!CREDENTIAL_POLICIES.includes(value)) {
|
|
1757
|
+
console.error(`Unknown credential policy "${value}". Use one of: ${CREDENTIAL_POLICIES.join(', ')}`);
|
|
1758
|
+
process.exit(1);
|
|
1759
|
+
}
|
|
1760
|
+
await registry.setCredentialPolicy(value);
|
|
1761
|
+
console.log(`Credential policy: ${value}`);
|
|
1762
|
+
break;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1230
1765
|
case '--help':
|
|
1231
1766
|
case '-h':
|
|
1232
1767
|
case 'help':
|