@agentxm/workspace-configuration 0.28.4-bootstrap.0

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.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Inline MCP capability policy: parsing and validating inline server
3
+ * definitions from user-supplied command, URL, environment, and header
4
+ * inputs, and deciding whether a configured entry already matches a desired
5
+ * inline definition.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import type { ConfigurableAgentId } from "@agentxm/extension-model/unstable/agent-capabilities";
11
+ import type { McpServerEntry } from "@agentxm/workspace-state";
12
+ import { WorkspaceConfigurationFailed } from "./errors.js";
13
+ import type { InlineMcpDefinition } from "./mcp-import-preflight.js";
14
+ export declare const splitCommand: (value: string) => ReadonlyArray<string>;
15
+ export declare const parseInlineMcpEnv: (values: ReadonlyArray<string>) => Effect.Effect<Readonly<Record<string, string>>, WorkspaceConfigurationFailed>;
16
+ export declare const parseInlineMcpHeaders: (values: ReadonlyArray<string>) => Effect.Effect<Readonly<Record<string, string>>, WorkspaceConfigurationFailed>;
17
+ export declare const validateInlineMcpRemoteUrl: (value: string) => Effect.Effect<void, WorkspaceConfigurationFailed>;
18
+ export declare const matchesInlineMcpEntry: (args: {
19
+ readonly existing: McpServerEntry | undefined;
20
+ readonly definition: InlineMcpDefinition;
21
+ readonly env: Readonly<Record<string, string>>;
22
+ readonly agents: ReadonlyArray<ConfigurableAgentId> | undefined;
23
+ }) => boolean;
24
+ /** Derive the inline definition from the add command's mutually exclusive inputs. */
25
+ export declare const makeInlineMcpDefinition: (args: {
26
+ readonly command: string | undefined;
27
+ readonly url: string | undefined;
28
+ }, headers: Readonly<Record<string, string>>) => Effect.Effect<InlineMcpDefinition, WorkspaceConfigurationFailed>;
29
+ //# sourceMappingURL=inline-mcp.d.ts.map
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Inline MCP capability policy: parsing and validating inline server
3
+ * definitions from user-supplied command, URL, environment, and header
4
+ * inputs, and deciding whether a configured entry already matches a desired
5
+ * inline definition.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import { WorkspaceConfigurationFailed } from "./errors.js";
11
+ export const splitCommand = (value) => value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => {
12
+ if ((part.startsWith('"') && part.endsWith('"')) ||
13
+ (part.startsWith("'") && part.endsWith("'"))) {
14
+ return part.slice(1, -1);
15
+ }
16
+ return part;
17
+ }) ?? [];
18
+ const isSensitiveName = (name) => /(?:authorization|cookie|credential|password|secret|token|api[-_]?key)/iu.test(name);
19
+ const hasEnvironmentReference = (value) => /\$\{[A-Za-z_][A-Za-z0-9_]*\}/u.test(value);
20
+ export const parseInlineMcpEnv = (values) => Effect.forEach(values, (value) => Effect.gen(function* () {
21
+ const separator = value.indexOf("=");
22
+ if (separator > 0) {
23
+ const name = value.slice(0, separator);
24
+ const configured = value.slice(separator + 1);
25
+ if (isSensitiveName(name) && !hasEnvironmentReference(configured)) {
26
+ return yield* new WorkspaceConfigurationFailed({
27
+ category: "usage",
28
+ detail: `Sensitive MCP input ${name} must use an environment reference; pass --env ${name}`,
29
+ });
30
+ }
31
+ return [name, configured];
32
+ }
33
+ return [value, `\${${value}}`];
34
+ })).pipe(Effect.map((entries) => Object.fromEntries(entries)));
35
+ const parseInlineMcpHeader = (value) => Effect.gen(function* () {
36
+ const separator = value.indexOf(":");
37
+ if (separator <= 0) {
38
+ return yield* new WorkspaceConfigurationFailed({
39
+ category: "usage",
40
+ detail: `Invalid header "${value}". Use Name:Value.`,
41
+ });
42
+ }
43
+ const name = value.slice(0, separator).trim();
44
+ const configured = value.slice(separator + 1).trim();
45
+ if (isSensitiveName(name) && !hasEnvironmentReference(configured)) {
46
+ return yield* new WorkspaceConfigurationFailed({
47
+ category: "usage",
48
+ detail: `Sensitive MCP header ${name} must use an environment reference`,
49
+ });
50
+ }
51
+ return [name, configured];
52
+ });
53
+ export const parseInlineMcpHeaders = (values) => Effect.map(Effect.forEach(values, parseInlineMcpHeader), (entries) => Object.fromEntries(entries));
54
+ export const validateInlineMcpRemoteUrl = (value) => Effect.gen(function* () {
55
+ const protocol = yield* Effect.try({
56
+ try: () => new URL(value).protocol,
57
+ catch: (cause) => new WorkspaceConfigurationFailed({
58
+ category: "usage",
59
+ detail: `Invalid MCP server URL "${value}". Use an http(s):// streamable URL.`,
60
+ cause,
61
+ }),
62
+ });
63
+ if (protocol === "ws:" || protocol === "wss:") {
64
+ return yield* new WorkspaceConfigurationFailed({
65
+ category: "usage",
66
+ detail: "WebSocket MCP transport is not supported; use an http(s):// streamable URL.",
67
+ });
68
+ }
69
+ if (protocol !== "http:" && protocol !== "https:") {
70
+ return yield* new WorkspaceConfigurationFailed({
71
+ category: "usage",
72
+ detail: `Unsupported MCP server URL scheme "${protocol}". Use an http(s):// streamable URL.`,
73
+ });
74
+ }
75
+ });
76
+ const arraysEqual = (left, right) => {
77
+ const normalizedLeft = left ?? [];
78
+ const normalizedRight = right ?? [];
79
+ return (normalizedLeft.length === normalizedRight.length &&
80
+ normalizedLeft.every((value, index) => value === normalizedRight[index]));
81
+ };
82
+ const recordsEqual = (left, right) => {
83
+ const normalizedLeft = left ?? {};
84
+ const normalizedRight = right ?? {};
85
+ const leftEntries = Object.entries(normalizedLeft).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
86
+ const rightEntries = Object.entries(normalizedRight).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
87
+ return (leftEntries.length === rightEntries.length &&
88
+ leftEntries.every(([key, value], index) => {
89
+ const rightEntry = rightEntries[index];
90
+ return rightEntry !== undefined && key === rightEntry[0] && value === rightEntry[1];
91
+ }));
92
+ };
93
+ export const matchesInlineMcpEntry = (args) => args.existing !== undefined &&
94
+ args.existing.kind === "inline" &&
95
+ args.existing.enabled &&
96
+ args.existing.command ===
97
+ (args.definition.type === "stdio" ? args.definition.command : undefined) &&
98
+ arraysEqual(args.existing.args, args.definition.type === "stdio" ? args.definition.args : undefined) &&
99
+ args.existing.url === (args.definition.type === "http" ? args.definition.url : undefined) &&
100
+ recordsEqual(args.existing.headers, args.definition.type === "http" ? args.definition.headers : undefined) &&
101
+ recordsEqual(args.existing.env, args.env) &&
102
+ arraysEqual(args.existing.agents, args.agents);
103
+ /** Derive the inline definition from the add command's mutually exclusive inputs. */
104
+ export const makeInlineMcpDefinition = (args, headers) => Effect.gen(function* () {
105
+ if (args.command !== undefined) {
106
+ const commandParts = splitCommand(args.command);
107
+ const command = commandParts[0];
108
+ if (command === undefined) {
109
+ return yield* new WorkspaceConfigurationFailed({
110
+ category: "usage",
111
+ detail: "Inline MCP command cannot be empty.",
112
+ });
113
+ }
114
+ return {
115
+ type: "stdio",
116
+ command,
117
+ args: commandParts.slice(1),
118
+ };
119
+ }
120
+ if (args.url !== undefined) {
121
+ return {
122
+ type: "http",
123
+ url: args.url,
124
+ headers,
125
+ };
126
+ }
127
+ return yield* new WorkspaceConfigurationFailed({
128
+ category: "usage",
129
+ detail: "Provide --command or --url for inline MCP servers.",
130
+ });
131
+ });
132
+ //# sourceMappingURL=inline-mcp.js.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Instruction-management policy over the kernel's instruction semantics:
3
+ * observation, readiness preflight, reconciliation transitions, and disabling
4
+ * instruction-file management. Failures stay typed through this feature; the
5
+ * application boundary owns their rendering.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import * as Option from "effect/Option";
11
+ import type { WorkspaceMutationsService } from "@agentxm/workspace-state";
12
+ import { assertInstructionTargetsSafe, assertInstructionsGitignoreSafe } from "@agentxm/extension-workspace";
13
+ import type { InstructionProjectionSnapshot, ResolvedInstructionsConfig } from "@agentxm/extension-workspace";
14
+ import { WorkspaceConfigurationFailed } from "./errors.js";
15
+ /** The one observation a command's planning derives its views from. */
16
+ export declare const observeInstructions: (args: {
17
+ readonly ws: WorkspaceMutationsService;
18
+ readonly config: ResolvedInstructionsConfig;
19
+ }) => Effect.Effect<InstructionProjectionSnapshot, import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
20
+ export declare const activeInstructionsConfig: (ws: WorkspaceMutationsService) => Effect.Effect<Option.Option<ResolvedInstructionsConfig>, import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, never>;
21
+ export declare const instructionStateIsCurrent: (snapshot: InstructionProjectionSnapshot) => boolean;
22
+ /** The typed failure a readiness preflight can surface. */
23
+ export type InstructionReadinessFailure = Effect.Error<ReturnType<typeof assertInstructionTargetsSafe>> | Effect.Error<ReturnType<typeof assertInstructionsGitignoreSafe>>;
24
+ export declare const instructionReconciliationReadiness: (args: {
25
+ readonly ws: WorkspaceMutationsService;
26
+ readonly snapshot: InstructionProjectionSnapshot;
27
+ }) => Effect.Effect<Option.None<InstructionReadinessFailure> | Option.Some<InstructionReadinessFailure>, never, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
28
+ /**
29
+ * Remove every alias the given configuration owns, observing fresh so the
30
+ * decision reflects the workspace at the moment of removal. Used before a new
31
+ * configuration is reconciled, so a changed source filename or alias policy
32
+ * never leaves the old arrangement behind. Refuses on an unowned target like
33
+ * every other path.
34
+ */
35
+ export declare const removeInstructionTargetsFor: (args: {
36
+ readonly ws: WorkspaceMutationsService;
37
+ readonly config: ResolvedInstructionsConfig;
38
+ }) => Effect.Effect<string[], import("@agentxm/workspace-state").WorkspaceSnapshotError | import("@agentxm/extension-workspace").InstructionMaintenanceFailed | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
39
+ /**
40
+ * Runs inside the workspace transaction: preflight against a fresh
41
+ * observation (the plan's readiness check ran before the transaction opened),
42
+ * apply the transition, reconcile, and verify from the sync's own readback.
43
+ */
44
+ export declare const reconcileInstructionTransition: <A, E>(args: {
45
+ readonly ws: WorkspaceMutationsService;
46
+ readonly config: ResolvedInstructionsConfig;
47
+ readonly preflightConfig?: ResolvedInstructionsConfig;
48
+ readonly transition: Effect.Effect<A, E>;
49
+ }) => Effect.Effect<A, WorkspaceConfigurationFailed | import("@agentxm/extension-workspace").InstructionMaintenanceFailure | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure | E, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
50
+ export declare const disableInstructionManagement: (args: {
51
+ readonly ws: WorkspaceMutationsService;
52
+ readonly config: ResolvedInstructionsConfig;
53
+ }) => Effect.Effect<{
54
+ removed: string[];
55
+ gitignore: string | undefined;
56
+ }, import("@agentxm/workspace-state").WorkspaceSnapshotError | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/extension-workspace").InstructionMaintenanceFailed | import("@agentxm/workspace-state").PathTraversalDetected | import("@agentxm/workspace-state").SymlinkCreationError | import("@agentxm/workspace-state").WorkspaceRootEscape | import("@agentxm/workspace-state").SettingsIoError | import("@agentxm/workspace-state").SettingsParseError | import("@agentxm/workspace-state").SettingsDecodeError, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
57
+ //# sourceMappingURL=instruction-reconciliation.d.ts.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Instruction-management policy over the kernel's instruction semantics:
3
+ * observation, readiness preflight, reconciliation transitions, and disabling
4
+ * instruction-file management. Failures stay typed through this feature; the
5
+ * application boundary owns their rendering.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import * as Option from "effect/Option";
11
+ import { assertInstructionTargetsSafe, assertInstructionsGitignoreSafe, instructionProjectionIsCurrent, observeInstructionProjection, removeManagedInstructionTargets, removeInstructionsGitignore, resolveInstructionsConfig, syncInstructions, } from "@agentxm/extension-workspace";
12
+ import { WorkspaceConfigurationFailed } from "./errors.js";
13
+ const configuredAgents = (ws) => ws.getConfiguredAgents();
14
+ /** The one observation a command's planning derives its views from. */
15
+ export const observeInstructions = Effect.fn("Instructions.observe")(function* (args) {
16
+ const agents = yield* configuredAgents(args.ws);
17
+ return yield* observeInstructionProjection({
18
+ workspaceRoot: args.ws.baseDir,
19
+ scope: args.ws.scope,
20
+ configuredAgents: agents,
21
+ config: args.config,
22
+ });
23
+ });
24
+ export const activeInstructionsConfig = Effect.fn("Instructions.activeConfig")(function* (ws) {
25
+ const value = yield* ws.getInstructionsConfig();
26
+ if (Option.isNone(value) || value.value === false) {
27
+ return Option.none();
28
+ }
29
+ return Option.some(resolveInstructionsConfig(value.value));
30
+ });
31
+ export const instructionStateIsCurrent = (snapshot) => snapshot.status.missingSources.length === 0 && instructionProjectionIsCurrent(snapshot);
32
+ export const instructionReconciliationReadiness = Effect.fn("Instructions.reconciliationReadiness")(function* (args) {
33
+ return yield* Effect.result(Effect.all([
34
+ assertInstructionTargetsSafe(args.snapshot.status),
35
+ assertInstructionsGitignoreSafe(args.ws.baseDir),
36
+ ], { concurrency: 1, discard: true })).pipe(Effect.map((result) => result._tag === "Success"
37
+ ? Option.none()
38
+ : Option.some(result.failure)));
39
+ });
40
+ /**
41
+ * Remove every alias the given configuration owns, observing fresh so the
42
+ * decision reflects the workspace at the moment of removal. Used before a new
43
+ * configuration is reconciled, so a changed source filename or alias policy
44
+ * never leaves the old arrangement behind. Refuses on an unowned target like
45
+ * every other path.
46
+ */
47
+ export const removeInstructionTargetsFor = (args) => Effect.gen(function* () {
48
+ const snapshot = yield* observeInstructions(args);
49
+ return yield* removeManagedInstructionTargets({ snapshot, dryRun: false });
50
+ });
51
+ /**
52
+ * Runs inside the workspace transaction: preflight against a fresh
53
+ * observation (the plan's readiness check ran before the transaction opened),
54
+ * apply the transition, reconcile, and verify from the sync's own readback.
55
+ */
56
+ export const reconcileInstructionTransition = (args) => Effect.gen(function* () {
57
+ const agents = yield* configuredAgents(args.ws);
58
+ const preflight = yield* observeInstructions({
59
+ ws: args.ws,
60
+ config: args.preflightConfig ?? args.config,
61
+ });
62
+ yield* assertInstructionTargetsSafe(preflight.status);
63
+ yield* assertInstructionsGitignoreSafe(args.ws.baseDir);
64
+ const transitionResult = yield* args.transition;
65
+ const syncResult = yield* syncInstructions({
66
+ workspaceRoot: args.ws.baseDir,
67
+ scope: args.ws.scope,
68
+ configuredAgents: agents,
69
+ config: args.config,
70
+ dryRun: false,
71
+ });
72
+ if (!instructionProjectionIsCurrent(syncResult.snapshot)) {
73
+ return yield* new WorkspaceConfigurationFailed({
74
+ category: "internal",
75
+ detail: "Instruction reconciliation did not reach the desired state",
76
+ });
77
+ }
78
+ return transitionResult;
79
+ }).pipe(Effect.withSpan("Instructions.reconcileTransition"));
80
+ export const disableInstructionManagement = Effect.fn("Instructions.disableManagement")(function* (args) {
81
+ yield* assertInstructionsGitignoreSafe(args.ws.baseDir);
82
+ const removed = yield* removeInstructionTargetsFor(args);
83
+ const gitignore = yield* removeInstructionsGitignore({
84
+ workspaceRoot: args.ws.baseDir,
85
+ dryRun: false,
86
+ });
87
+ yield* args.ws.setInstructionsConfig(false);
88
+ return {
89
+ removed,
90
+ gitignore: Option.getOrUndefined(gitignore),
91
+ };
92
+ });
93
+ //# sourceMappingURL=instruction-reconciliation.js.map
@@ -0,0 +1,44 @@
1
+ import * as DateTime from "effect/DateTime";
2
+ import type { ConfigurableAgentId } from "@agentxm/extension-model/unstable/agent-capabilities";
3
+ export type InlineMcpDefinition = {
4
+ readonly type: "stdio";
5
+ readonly command: string;
6
+ readonly args: ReadonlyArray<string>;
7
+ } | {
8
+ readonly type: "http";
9
+ readonly url: string;
10
+ readonly headers: Readonly<Record<string, string>>;
11
+ };
12
+ export interface McpImportSource {
13
+ readonly filePath: string;
14
+ readonly serversKey: string;
15
+ readonly config: Readonly<Record<string, unknown>>;
16
+ readonly agents?: ReadonlyArray<ConfigurableAgentId>;
17
+ }
18
+ export interface McpImportAdoption {
19
+ readonly filePath: string;
20
+ readonly serversKey: string;
21
+ readonly name: string;
22
+ }
23
+ export interface McpImportCandidate {
24
+ readonly name: string;
25
+ readonly definition: InlineMcpDefinition;
26
+ readonly env: Readonly<Record<string, string>>;
27
+ readonly adoptions: ReadonlyArray<McpImportAdoption>;
28
+ readonly agents?: ReadonlyArray<ConfigurableAgentId>;
29
+ }
30
+ export interface McpImportFinding {
31
+ readonly name: string;
32
+ readonly reason: string;
33
+ }
34
+ export interface McpImportPreflight {
35
+ readonly candidates: ReadonlyArray<McpImportCandidate>;
36
+ readonly skipped: ReadonlyArray<McpImportFinding>;
37
+ readonly conflicts: ReadonlyArray<McpImportFinding>;
38
+ }
39
+ export declare const preflightMcpImports: (args: {
40
+ readonly configuredNames: ReadonlySet<string>;
41
+ readonly now: DateTime.Utc;
42
+ readonly sources: ReadonlyArray<McpImportSource>;
43
+ }) => McpImportPreflight;
44
+ //# sourceMappingURL=mcp-import-preflight.d.ts.map
@@ -0,0 +1,277 @@
1
+ import * as DateTime from "effect/DateTime";
2
+ import { isAxmManagedMcpEntry } from "@agentxm/workspace-state";
3
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const stringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string") ? value : undefined;
5
+ const stringRecord = (value) => {
6
+ if (!isRecord(value))
7
+ return undefined;
8
+ const entries = Object.entries(value);
9
+ if (entries.some(([, item]) => typeof item !== "string"))
10
+ return undefined;
11
+ return Object.fromEntries(entries.map(([key, item]) => [key, String(item)]));
12
+ };
13
+ const sortedRecord = (value) => Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
14
+ const envRefs = (value) => {
15
+ const env = value === undefined ? {} : stringRecord(value);
16
+ if (env === undefined)
17
+ return undefined;
18
+ return Object.fromEntries(Object.keys(env)
19
+ .sort((left, right) => left.localeCompare(right))
20
+ .map((name) => [name, `\${${name}}`]));
21
+ };
22
+ const isSensitiveName = (name) => /(?:authorization|cookie|credential|password|secret|token|api[-_]?key)/iu.test(name);
23
+ const hasEnvironmentReference = (value) => /\$\{[A-Za-z_][A-Za-z0-9_]*\}/u.test(value);
24
+ const literalSensitiveField = (config) => {
25
+ const entry = Object.entries(config).find(([name, value]) => isSensitiveName(name) && typeof value === "string" && !hasEnvironmentReference(value));
26
+ return entry === undefined
27
+ ? undefined
28
+ : {
29
+ name: entry[0],
30
+ reason: `Sensitive field ${entry[0]} must use an environment reference`,
31
+ };
32
+ };
33
+ const sensitiveArgumentConflict = (args) => {
34
+ for (let index = 0; index < args.length; index += 1) {
35
+ const argument = args[index] ?? "";
36
+ const assignment = /^(?:--)?([^=]+)=(.*)$/u.exec(argument);
37
+ const assignedName = assignment?.[1];
38
+ const assignedValue = assignment?.[2];
39
+ if (assignedName !== undefined &&
40
+ assignedValue !== undefined &&
41
+ isSensitiveName(assignedName) &&
42
+ !hasEnvironmentReference(assignedValue)) {
43
+ return {
44
+ name: assignedName,
45
+ reason: `Sensitive argument ${assignedName} must use an environment reference`,
46
+ };
47
+ }
48
+ const flagName = argument.replace(/^-+/u, "");
49
+ if (assignment === null && argument.startsWith("-") && isSensitiveName(flagName)) {
50
+ const value = args[index + 1];
51
+ if (value === undefined || !hasEnvironmentReference(value)) {
52
+ return {
53
+ name: flagName,
54
+ reason: `Sensitive argument ${flagName} must use an environment reference`,
55
+ };
56
+ }
57
+ index += 1;
58
+ }
59
+ }
60
+ return undefined;
61
+ };
62
+ const sensitiveHeaderConflict = (headers) => {
63
+ const entry = Object.entries(headers).find(([name, value]) => isSensitiveName(name) && !hasEnvironmentReference(value));
64
+ return entry === undefined
65
+ ? undefined
66
+ : {
67
+ name: entry[0],
68
+ reason: `Sensitive header ${entry[0]} must use an environment reference`,
69
+ };
70
+ };
71
+ const sensitiveUrlConflict = (value) => {
72
+ try {
73
+ const url = new URL(value);
74
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
75
+ return "Unsupported MCP server URL scheme; use an http(s) URL";
76
+ }
77
+ const username = decodeURIComponent(url.username);
78
+ const password = decodeURIComponent(url.password);
79
+ if ((username.length > 0 && !hasEnvironmentReference(username)) ||
80
+ (password.length > 0 && !hasEnvironmentReference(password))) {
81
+ return "MCP server URL credentials must use an environment reference";
82
+ }
83
+ const sensitiveParameter = Array.from(url.searchParams.keys()).find(isSensitiveName);
84
+ if (sensitiveParameter !== undefined &&
85
+ !hasEnvironmentReference(url.searchParams.get(sensitiveParameter) ?? "")) {
86
+ return `Sensitive URL parameter ${sensitiveParameter} must use an environment reference`;
87
+ }
88
+ return undefined;
89
+ }
90
+ catch {
91
+ return "Unsupported MCP server URL";
92
+ }
93
+ };
94
+ const normalizeServer = (args) => {
95
+ if (isAxmManagedMcpEntry(args.config)) {
96
+ return { _tag: "skip", finding: { name: args.name, reason: "Already managed by AXM" } };
97
+ }
98
+ const sensitiveField = literalSensitiveField(args.config);
99
+ if (sensitiveField !== undefined) {
100
+ return {
101
+ _tag: "conflict",
102
+ finding: { name: args.name, reason: sensitiveField.reason },
103
+ };
104
+ }
105
+ const env = envRefs(args.config["env"] ?? args.config["environment"]);
106
+ if (env === undefined) {
107
+ return {
108
+ _tag: "skip",
109
+ finding: { name: args.name, reason: "Unsupported MCP server environment" },
110
+ };
111
+ }
112
+ const url = args.config["url"];
113
+ if (typeof url === "string") {
114
+ const rawHeaders = args.config["headers"] ?? args.config["http_headers"];
115
+ const headers = rawHeaders === undefined ? {} : stringRecord(rawHeaders);
116
+ if (headers === undefined) {
117
+ return {
118
+ _tag: "skip",
119
+ finding: { name: args.name, reason: "Unsupported MCP server headers" },
120
+ };
121
+ }
122
+ const headerConflict = sensitiveHeaderConflict(headers);
123
+ if (headerConflict !== undefined) {
124
+ return {
125
+ _tag: "conflict",
126
+ finding: { name: args.name, reason: headerConflict.reason },
127
+ };
128
+ }
129
+ const urlConflict = sensitiveUrlConflict(url);
130
+ if (urlConflict !== undefined) {
131
+ return { _tag: "conflict", finding: { name: args.name, reason: urlConflict } };
132
+ }
133
+ return {
134
+ _tag: "candidate",
135
+ candidate: {
136
+ name: args.name,
137
+ definition: {
138
+ type: "http",
139
+ url,
140
+ headers: sortedRecord(headers),
141
+ },
142
+ env,
143
+ adoptions: [args.adoption],
144
+ ...(args.agents === undefined ? {} : { agents: args.agents }),
145
+ },
146
+ };
147
+ }
148
+ const commandValue = args.config["command"];
149
+ const separateArgs = args.config["args"] === undefined ? [] : stringArray(args.config["args"]);
150
+ if (separateArgs === undefined) {
151
+ return {
152
+ _tag: "skip",
153
+ finding: { name: args.name, reason: "Unsupported MCP server arguments" },
154
+ };
155
+ }
156
+ const command = typeof commandValue === "string"
157
+ ? { executable: commandValue, args: separateArgs }
158
+ : (() => {
159
+ const parts = stringArray(commandValue);
160
+ if (parts === undefined)
161
+ return undefined;
162
+ const executable = parts[0];
163
+ return executable === undefined ? undefined : { executable, args: parts.slice(1) };
164
+ })();
165
+ if (command === undefined || command.executable.length === 0) {
166
+ return {
167
+ _tag: "skip",
168
+ finding: { name: args.name, reason: "Unsupported MCP server configuration" },
169
+ };
170
+ }
171
+ const argumentConflict = sensitiveArgumentConflict(command.args);
172
+ if (argumentConflict !== undefined) {
173
+ return {
174
+ _tag: "conflict",
175
+ finding: { name: args.name, reason: argumentConflict.reason },
176
+ };
177
+ }
178
+ return {
179
+ _tag: "candidate",
180
+ candidate: {
181
+ name: args.name,
182
+ definition: {
183
+ type: "stdio",
184
+ command: command.executable,
185
+ args: command.args,
186
+ },
187
+ env,
188
+ adoptions: [args.adoption],
189
+ ...(args.agents === undefined ? {} : { agents: args.agents }),
190
+ },
191
+ };
192
+ };
193
+ const candidateIdentity = (candidate) => {
194
+ const entry = candidate.definition;
195
+ return JSON.stringify({
196
+ type: entry.type,
197
+ command: entry.type === "stdio" ? entry.command : undefined,
198
+ args: entry.type === "stdio" ? entry.args : undefined,
199
+ url: entry.type === "http" ? entry.url : undefined,
200
+ headers: entry.type === "http" ? entry.headers : undefined,
201
+ env: sortedRecord(candidate.env),
202
+ });
203
+ };
204
+ const sortFindings = (findings) => [...findings].sort((left, right) => left.name.localeCompare(right.name) || left.reason.localeCompare(right.reason));
205
+ export const preflightMcpImports = (args) => {
206
+ const candidates = new Map();
207
+ const conflictNames = new Set();
208
+ const skipped = [];
209
+ const conflicts = [];
210
+ const sources = [...args.sources].sort((left, right) => left.filePath.localeCompare(right.filePath));
211
+ for (const source of sources) {
212
+ const servers = source.config[source.serversKey];
213
+ if (!isRecord(servers))
214
+ continue;
215
+ for (const [name, value] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
216
+ if (args.configuredNames.has(name)) {
217
+ skipped.push({ name, reason: "Already configured" });
218
+ continue;
219
+ }
220
+ if (!isRecord(value)) {
221
+ skipped.push({ name, reason: "Unsupported MCP server configuration" });
222
+ continue;
223
+ }
224
+ const normalized = normalizeServer({
225
+ name,
226
+ config: value,
227
+ adoption: { filePath: source.filePath, serversKey: source.serversKey, name },
228
+ now: args.now,
229
+ ...(source.agents === undefined ? {} : { agents: source.agents }),
230
+ });
231
+ if (normalized._tag === "skip") {
232
+ skipped.push(normalized.finding);
233
+ continue;
234
+ }
235
+ if (normalized._tag === "conflict") {
236
+ conflictNames.add(name);
237
+ conflicts.push(normalized.finding);
238
+ continue;
239
+ }
240
+ const existing = candidates.get(name);
241
+ if (existing === undefined) {
242
+ candidates.set(name, normalized.candidate);
243
+ continue;
244
+ }
245
+ if (candidateIdentity(existing) !== candidateIdentity(normalized.candidate)) {
246
+ conflictNames.add(name);
247
+ conflicts.push({
248
+ name,
249
+ reason: `Conflicting unmanaged configurations were found for ${name}`,
250
+ });
251
+ continue;
252
+ }
253
+ candidates.set(name, {
254
+ ...existing,
255
+ adoptions: Array.from(new Map([...existing.adoptions, ...normalized.candidate.adoptions].map((adoption) => [
256
+ `${adoption.filePath}\0${adoption.serversKey}\0${adoption.name}`,
257
+ adoption,
258
+ ])).values()),
259
+ ...(existing.agents === undefined && normalized.candidate.agents === undefined
260
+ ? {}
261
+ : {
262
+ agents: Array.from(new Set([...(existing.agents ?? []), ...(normalized.candidate.agents ?? [])])).sort((left, right) => left.localeCompare(right)),
263
+ }),
264
+ });
265
+ }
266
+ }
267
+ const uniqueConflicts = Array.from(new Map(sortFindings(conflicts).map((finding) => [`${finding.name}\0${finding.reason}`, finding])).values());
268
+ const uniqueSkipped = Array.from(new Map(sortFindings(skipped).map((finding) => [`${finding.name}\0${finding.reason}`, finding])).values());
269
+ return {
270
+ candidates: Array.from(candidates.values())
271
+ .filter((candidate) => !conflictNames.has(candidate.name))
272
+ .sort((left, right) => left.name.localeCompare(right.name)),
273
+ skipped: uniqueSkipped,
274
+ conflicts: uniqueConflicts,
275
+ };
276
+ };
277
+ //# sourceMappingURL=mcp-import-preflight.js.map