@1mcp/agent 0.34.4 → 0.35.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/build/admin/assets/admin-console-CX5GzncA.css +1 -0
  2. package/build/admin/assets/admin-console-CkZ5lBKg.js +61 -0
  3. package/build/admin/index.html +13 -0
  4. package/build/commands/admin/admin.d.ts +90 -0
  5. package/build/commands/admin/admin.d.ts.map +1 -0
  6. package/build/commands/admin/admin.js +545 -0
  7. package/build/commands/admin/admin.js.map +1 -0
  8. package/build/commands/admin/index.d.ts +3 -0
  9. package/build/commands/admin/index.d.ts.map +1 -0
  10. package/build/commands/admin/index.js +99 -0
  11. package/build/commands/admin/index.js.map +1 -0
  12. package/build/commands/auth/index.d.ts.map +1 -1
  13. package/build/commands/auth/index.js +24 -5
  14. package/build/commands/auth/index.js.map +1 -1
  15. package/build/commands/auth/login.d.ts +1 -0
  16. package/build/commands/auth/login.d.ts.map +1 -1
  17. package/build/commands/auth/login.js +6 -10
  18. package/build/commands/auth/login.js.map +1 -1
  19. package/build/commands/auth/logout.d.ts +3 -0
  20. package/build/commands/auth/logout.d.ts.map +1 -1
  21. package/build/commands/auth/logout.js +19 -32
  22. package/build/commands/auth/logout.js.map +1 -1
  23. package/build/commands/auth/runtimeTargetContext.d.ts +25 -0
  24. package/build/commands/auth/runtimeTargetContext.d.ts.map +1 -0
  25. package/build/commands/auth/runtimeTargetContext.js +69 -0
  26. package/build/commands/auth/runtimeTargetContext.js.map +1 -0
  27. package/build/commands/auth/status.d.ts +1 -0
  28. package/build/commands/auth/status.d.ts.map +1 -1
  29. package/build/commands/auth/status.js +10 -33
  30. package/build/commands/auth/status.js.map +1 -1
  31. package/build/commands/inspect/index.d.ts.map +1 -1
  32. package/build/commands/inspect/index.js +4 -0
  33. package/build/commands/inspect/index.js.map +1 -1
  34. package/build/commands/inspect/inspect.d.ts +1 -0
  35. package/build/commands/inspect/inspect.d.ts.map +1 -1
  36. package/build/commands/inspect/inspect.js +2 -2
  37. package/build/commands/inspect/inspect.js.map +1 -1
  38. package/build/commands/instructions/index.d.ts.map +1 -1
  39. package/build/commands/instructions/index.js +4 -0
  40. package/build/commands/instructions/index.js.map +1 -1
  41. package/build/commands/instructions/instructions.d.ts +1 -0
  42. package/build/commands/instructions/instructions.d.ts.map +1 -1
  43. package/build/commands/instructions/instructions.js.map +1 -1
  44. package/build/commands/instructions/instructionsUtils.js +1 -1
  45. package/build/commands/instructions/instructionsUtils.js.map +1 -1
  46. package/build/commands/mcp/enable.d.ts +89 -2
  47. package/build/commands/mcp/enable.d.ts.map +1 -1
  48. package/build/commands/mcp/enable.js +555 -35
  49. package/build/commands/mcp/enable.js.map +1 -1
  50. package/build/commands/proxy/index.d.ts +1 -0
  51. package/build/commands/proxy/index.d.ts.map +1 -1
  52. package/build/commands/proxy/index.js +4 -0
  53. package/build/commands/proxy/index.js.map +1 -1
  54. package/build/commands/run/index.d.ts.map +1 -1
  55. package/build/commands/run/index.js +4 -0
  56. package/build/commands/run/index.js.map +1 -1
  57. package/build/commands/run/run.d.ts +1 -0
  58. package/build/commands/run/run.d.ts.map +1 -1
  59. package/build/commands/run/run.js +2 -2
  60. package/build/commands/run/run.js.map +1 -1
  61. package/build/commands/serve/serve.d.ts.map +1 -1
  62. package/build/commands/serve/serve.js +4 -0
  63. package/build/commands/serve/serve.js.map +1 -1
  64. package/build/commands/shared/apiClient.d.ts +5 -1
  65. package/build/commands/shared/apiClient.d.ts.map +1 -1
  66. package/build/commands/shared/apiClient.js +17 -9
  67. package/build/commands/shared/apiClient.js.map +1 -1
  68. package/build/commands/shared/clientSurfaceAttachment.d.ts +14 -0
  69. package/build/commands/shared/clientSurfaceAttachment.d.ts.map +1 -1
  70. package/build/commands/shared/clientSurfaceAttachment.js +51 -9
  71. package/build/commands/shared/clientSurfaceAttachment.js.map +1 -1
  72. package/build/commands/shared/commandRunner.d.ts +3 -1
  73. package/build/commands/shared/commandRunner.d.ts.map +1 -1
  74. package/build/commands/shared/commandRunner.js +59 -2
  75. package/build/commands/shared/commandRunner.js.map +1 -1
  76. package/build/commands/shared/serveTargetResolver.d.ts +14 -1
  77. package/build/commands/shared/serveTargetResolver.d.ts.map +1 -1
  78. package/build/commands/shared/serveTargetResolver.js +145 -3
  79. package/build/commands/shared/serveTargetResolver.js.map +1 -1
  80. package/build/commands/target/index.d.ts +3 -0
  81. package/build/commands/target/index.d.ts.map +1 -0
  82. package/build/commands/target/index.js +171 -0
  83. package/build/commands/target/index.js.map +1 -0
  84. package/build/commands/target/target.d.ts +78 -0
  85. package/build/commands/target/target.d.ts.map +1 -0
  86. package/build/commands/target/target.js +471 -0
  87. package/build/commands/target/target.js.map +1 -0
  88. package/build/constants/mcp.d.ts +1 -1
  89. package/build/constants/mcp.d.ts.map +1 -1
  90. package/build/constants/mcp.js +1 -1
  91. package/build/constants/mcp.js.map +1 -1
  92. package/build/core/runtime/runtimeIdentityService.d.ts +30 -0
  93. package/build/core/runtime/runtimeIdentityService.d.ts.map +1 -0
  94. package/build/core/runtime/runtimeIdentityService.js +63 -0
  95. package/build/core/runtime/runtimeIdentityService.js.map +1 -0
  96. package/build/core/server/agentConfig.d.ts +4 -0
  97. package/build/core/server/agentConfig.d.ts.map +1 -1
  98. package/build/core/server/agentConfig.js +3 -0
  99. package/build/core/server/agentConfig.js.map +1 -1
  100. package/build/core/tools/handlers/serverManagementHandler.d.ts +7 -7
  101. package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts +2 -2
  102. package/build/core/tools/internal/schemas/discovery.d.ts +1 -1
  103. package/build/core/tools/internal/schemas/index.d.ts +13 -13
  104. package/build/core/tools/internal/schemas/installation.d.ts +7 -7
  105. package/build/core/tools/internal/schemas/management.d.ts +5 -5
  106. package/build/core/types/transport.d.ts +9 -3
  107. package/build/core/types/transport.d.ts.map +1 -1
  108. package/build/core/types/transport.js +5 -0
  109. package/build/core/types/transport.js.map +1 -1
  110. package/build/domains/admin/adminConfiguredServerService.d.ts +275 -0
  111. package/build/domains/admin/adminConfiguredServerService.d.ts.map +1 -0
  112. package/build/domains/admin/adminConfiguredServerService.js +1926 -0
  113. package/build/domains/admin/adminConfiguredServerService.js.map +1 -0
  114. package/build/domains/admin/adminConnectivityChecker.d.ts +7 -0
  115. package/build/domains/admin/adminConnectivityChecker.d.ts.map +1 -0
  116. package/build/domains/admin/adminConnectivityChecker.js +42 -0
  117. package/build/domains/admin/adminConnectivityChecker.js.map +1 -0
  118. package/build/domains/admin/adminDomain.d.ts +29 -0
  119. package/build/domains/admin/adminDomain.d.ts.map +1 -0
  120. package/build/domains/admin/adminDomain.js +39 -0
  121. package/build/domains/admin/adminDomain.js.map +1 -0
  122. package/build/domains/admin/adminIdentityService.d.ts +75 -0
  123. package/build/domains/admin/adminIdentityService.d.ts.map +1 -0
  124. package/build/domains/admin/adminIdentityService.js +321 -0
  125. package/build/domains/admin/adminIdentityService.js.map +1 -0
  126. package/build/domains/admin/adminOperationService.d.ts +202 -0
  127. package/build/domains/admin/adminOperationService.d.ts.map +1 -0
  128. package/build/domains/admin/adminOperationService.js +724 -0
  129. package/build/domains/admin/adminOperationService.js.map +1 -0
  130. package/build/domains/admin/adminPresetService.d.ts +140 -0
  131. package/build/domains/admin/adminPresetService.d.ts.map +1 -0
  132. package/build/domains/admin/adminPresetService.js +248 -0
  133. package/build/domains/admin/adminPresetService.js.map +1 -0
  134. package/build/domains/admin/runtimeScopeAdminLock.d.ts +22 -0
  135. package/build/domains/admin/runtimeScopeAdminLock.d.ts.map +1 -0
  136. package/build/domains/admin/runtimeScopeAdminLock.js +121 -0
  137. package/build/domains/admin/runtimeScopeAdminLock.js.map +1 -0
  138. package/build/domains/config-change/configChange.d.ts +6 -1
  139. package/build/domains/config-change/configChange.d.ts.map +1 -1
  140. package/build/domains/config-change/configChange.js +344 -0
  141. package/build/domains/config-change/configChange.js.map +1 -1
  142. package/build/domains/config-change/types.d.ts +18 -3
  143. package/build/domains/config-change/types.d.ts.map +1 -1
  144. package/build/domains/preset/manager/presetManager.d.ts +1 -0
  145. package/build/domains/preset/manager/presetManager.d.ts.map +1 -1
  146. package/build/domains/preset/manager/presetManager.js +3 -0
  147. package/build/domains/preset/manager/presetManager.js.map +1 -1
  148. package/build/domains/preset/tagAuthoring.d.ts +10 -0
  149. package/build/domains/preset/tagAuthoring.d.ts.map +1 -0
  150. package/build/domains/preset/tagAuthoring.js +94 -0
  151. package/build/domains/preset/tagAuthoring.js.map +1 -0
  152. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts +51 -0
  153. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts.map +1 -0
  154. package/build/domains/runtime-targets/runtimeIdentityVerification.js +258 -0
  155. package/build/domains/runtime-targets/runtimeIdentityVerification.js.map +1 -0
  156. package/build/domains/runtime-targets/runtimeTargetStore.d.ts +222 -0
  157. package/build/domains/runtime-targets/runtimeTargetStore.d.ts.map +1 -0
  158. package/build/domains/runtime-targets/runtimeTargetStore.js +1456 -0
  159. package/build/domains/runtime-targets/runtimeTargetStore.js.map +1 -0
  160. package/build/index.d.ts.map +1 -1
  161. package/build/index.js +7 -1
  162. package/build/index.js.map +1 -1
  163. package/build/transport/http/routes/adminRoutes.d.ts +34 -0
  164. package/build/transport/http/routes/adminRoutes.d.ts.map +1 -0
  165. package/build/transport/http/routes/adminRoutes.js +1217 -0
  166. package/build/transport/http/routes/adminRoutes.js.map +1 -0
  167. package/build/transport/http/routes/oauthRoutes.d.ts +2 -0
  168. package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
  169. package/build/transport/http/routes/oauthRoutes.js +5 -185
  170. package/build/transport/http/routes/oauthRoutes.js.map +1 -1
  171. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts +7 -0
  172. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts.map +1 -0
  173. package/build/transport/http/routes/runtimeIdentityRoutes.js +9 -0
  174. package/build/transport/http/routes/runtimeIdentityRoutes.js.map +1 -0
  175. package/build/transport/http/server.d.ts +3 -0
  176. package/build/transport/http/server.d.ts.map +1 -1
  177. package/build/transport/http/server.js +92 -3
  178. package/build/transport/http/server.js.map +1 -1
  179. package/build/utils/validation/urlDetection.d.ts +13 -2
  180. package/build/utils/validation/urlDetection.d.ts.map +1 -1
  181. package/build/utils/validation/urlDetection.js +37 -6
  182. package/build/utils/validation/urlDetection.js.map +1 -1
  183. package/package.json +22 -9
@@ -0,0 +1,1926 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mergeGlobalAndServerConfig } from '../../config/mcpConfigMerge.js';
3
+ import { GLOBAL_TRANSPORT_CONFIG_KEYS, transportConfigSchema, } from '../../core/types/index.js';
4
+ import { fingerprintConfiguredServerDefaults, fingerprintConfiguredServerSecretValue, fingerprintConfiguredServerTarget, isConfiguredServerTargetDisabled, } from '../../domains/config-change/configChange.js';
5
+ import { z } from 'zod';
6
+ const ENV_PLACEHOLDER_PATTERN = /\$\{[^}]+\}|\$[A-Za-z_][A-Za-z0-9_]*/u;
7
+ const previewTransportConfigSchema = transportConfigSchema.extend({
8
+ url: z
9
+ .string()
10
+ .refine((value) => ENV_PLACEHOLDER_PATTERN.test(value) || z.string().url().safeParse(value).success, {
11
+ message: 'URL must be a valid URL or environment substitution reference.',
12
+ })
13
+ .optional(),
14
+ });
15
+ export class AdminConfiguredServerNotFoundError extends Error {
16
+ targetName;
17
+ code = 'configured_server_not_found';
18
+ constructor(targetName) {
19
+ super(`Configured server target '${targetName}' was not found`);
20
+ this.targetName = targetName;
21
+ this.name = 'AdminConfiguredServerNotFoundError';
22
+ }
23
+ }
24
+ export class AdminConfiguredServerService {
25
+ options;
26
+ constructor(options) {
27
+ this.options = options;
28
+ }
29
+ async listConfiguredServers(input) {
30
+ return this.options.operationService.executeReadOnly({
31
+ context: input.context,
32
+ operationName: 'listConfiguredServers',
33
+ run: async () => ({ servers: this.readConfiguredServers() }),
34
+ });
35
+ }
36
+ async getConfiguredServerDetail(input) {
37
+ const context = {
38
+ ...input.context,
39
+ target: { type: 'configured_server', id: input.targetName },
40
+ };
41
+ return this.options.operationService.executeReadOnly({
42
+ context,
43
+ operationName: 'getConfiguredServerDetail',
44
+ run: async () => {
45
+ const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
46
+ const server = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
47
+ return {
48
+ server,
49
+ editContract: createConfiguredServerEditContract(server, currentConfig, serverDefaults),
50
+ };
51
+ },
52
+ });
53
+ }
54
+ async previewConfiguredServerEdit(input) {
55
+ const context = {
56
+ ...input.context,
57
+ target: { type: 'configured_server', id: input.targetName },
58
+ };
59
+ const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
60
+ return this.options.operationService.executeDryRun({
61
+ context,
62
+ operationName: 'previewConfiguredServerEdit',
63
+ run: async () => this.previewConfiguredServerEditResult(input, currentConfig, serverDefaults),
64
+ });
65
+ }
66
+ async applyConfiguredServerEdit(input) {
67
+ const context = {
68
+ ...input.context,
69
+ target: { type: 'configured_server', id: input.targetName },
70
+ confirmationFacts: sanitizeApplyConfirmationFacts(input.context.confirmationFacts),
71
+ };
72
+ return this.options.operationService.executeMutation({
73
+ context,
74
+ operationName: 'applyConfiguredServerEdit',
75
+ prepare: async () => {
76
+ const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
77
+ const preview = await this.previewConfiguredServerEditResult({ ...input, connectivityCheck: 'auto' }, currentConfig, serverDefaults, true);
78
+ const normalizedEdit = normalizeEditDraft(input.edit);
79
+ const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
80
+ const applicableEdit = filterApplicableSecretEdits(normalizedEdit.edit, currentReadModel.secretInputs);
81
+ const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults);
82
+ const proposedReadModel = createConfiguredServerReadModel(preview.proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig));
83
+ const connectionCritical = preview.diff.some((entry) => entry.riskFlags.includes('connection_critical'));
84
+ const secretChange = preview.diff.some((entry) => entry.riskFlags.includes('secret'));
85
+ if (preview.validation.status !== 'valid') {
86
+ throw new AdminConfiguredServerApplyError('configured_server_edit_invalid');
87
+ }
88
+ if (!preview.configChange.changed) {
89
+ throw new AdminConfiguredServerApplyError('configured_server_edit_unchanged');
90
+ }
91
+ if (preview.previewFingerprint !== input.previewFingerprint) {
92
+ throw new AdminConfiguredServerApplyError('configured_server_stale_preview');
93
+ }
94
+ if (connectionCritical &&
95
+ !wouldUseLocalStdioTransport(proposedConfig) &&
96
+ proposedReadModel.enabled &&
97
+ preview.connectivityCheck.status !== 'passed' &&
98
+ !(preview.connectivityCheck.status === 'failed' &&
99
+ input.context.confirmationFacts?.connectivityFailureOverrideConfirmed === true)) {
100
+ throw new AdminConfiguredServerApplyError('configured_server_connectivity_blocked');
101
+ }
102
+ return {
103
+ confirmationRequirements: [
104
+ { code: 'previewConfirmed', expected: input.previewFingerprint },
105
+ ...(preview.proposedTargetName !== input.targetName
106
+ ? [{ code: 'targetNameConfirmed', expected: preview.proposedTargetName }]
107
+ : []),
108
+ ...(secretChange ? [{ code: 'secretChangeConfirmed', expected: true }] : []),
109
+ ...(connectionCritical ? [{ code: 'connectionCriticalConfirmed', expected: true }] : []),
110
+ ...(preview.connectivityCheck.status === 'failed'
111
+ ? [{ code: 'connectivityFailureOverrideConfirmed', expected: true }]
112
+ : []),
113
+ ],
114
+ run: async () => {
115
+ const configChange = await this.options.configChangeService.editConfiguredServerTarget({
116
+ sourceName: input.targetName,
117
+ targetName: preview.proposedTargetName,
118
+ serverConfig: proposedConfig,
119
+ expectedSourceFingerprint: fingerprintConfiguredServerTarget(currentConfig),
120
+ expectedGlobalConfigFingerprint: fingerprintConfiguredServerDefaults(serverDefaults),
121
+ });
122
+ assertSuccessfulConfiguredServerEdit(configChange);
123
+ return {
124
+ originalTargetName: input.targetName,
125
+ targetName: preview.proposedTargetName,
126
+ previewFingerprint: preview.previewFingerprint,
127
+ configChange,
128
+ };
129
+ },
130
+ };
131
+ },
132
+ });
133
+ }
134
+ async enableConfiguredServer(input) {
135
+ return this.setConfiguredServerEnabledState(input, true);
136
+ }
137
+ async disableConfiguredServer(input) {
138
+ return this.setConfiguredServerEnabledState(input, false);
139
+ }
140
+ getRecentAuditFacts(options = {}) {
141
+ return this.options.operationService.getRecentAuditFacts(options);
142
+ }
143
+ async setConfiguredServerEnabledState(input, enabled) {
144
+ const operationName = enabled ? 'enableConfiguredServer' : 'disableConfiguredServer';
145
+ const context = {
146
+ ...input.context,
147
+ target: { type: 'configured_server', id: input.targetName },
148
+ };
149
+ if (input.dryRun) {
150
+ return this.options.operationService.executeDryRun({
151
+ context,
152
+ operationName,
153
+ run: async () => {
154
+ const configChange = await this.options.configChangeService.previewConfiguredServerTargetEnabledState({
155
+ targetName: input.targetName,
156
+ enabled,
157
+ backup: 'skip',
158
+ });
159
+ assertSuccessfulConfigChange(configChange);
160
+ return {
161
+ mode: 'dry_run',
162
+ targetName: input.targetName,
163
+ enabled,
164
+ outcome: mutationOutcome(enabled, configChange.changed),
165
+ configChange,
166
+ };
167
+ },
168
+ });
169
+ }
170
+ return this.options.operationService.executeMutation({
171
+ context,
172
+ operationName,
173
+ confirmationRequirements: input.confirmationRequirements,
174
+ run: async () => {
175
+ const configChange = await this.options.configChangeService.setConfiguredServerTargetEnabledState({
176
+ targetName: input.targetName,
177
+ enabled,
178
+ backup: 'required',
179
+ });
180
+ assertSuccessfulConfigChange(configChange);
181
+ return {
182
+ targetName: input.targetName,
183
+ enabled,
184
+ outcome: mutationOutcome(enabled, configChange.changed),
185
+ configChange,
186
+ };
187
+ },
188
+ });
189
+ }
190
+ readConfiguredServers() {
191
+ const parsed = this.options.readConfigDocument();
192
+ if (!parsed) {
193
+ return [];
194
+ }
195
+ return Object.entries(parsed.mcpServers ?? {}).map(([name, serverConfig]) => createConfiguredServerReadModel(name, mergeGlobalAndServerConfig(parsed.serverDefaults, serverConfig)));
196
+ }
197
+ readConfiguredServerState(targetName) {
198
+ const parsed = this.options.readConfigDocument();
199
+ const currentConfig = parsed?.mcpServers?.[targetName];
200
+ if (!currentConfig) {
201
+ throw new AdminConfiguredServerNotFoundError(targetName);
202
+ }
203
+ return { currentConfig, serverDefaults: parsed?.serverDefaults };
204
+ }
205
+ readConfiguredServerConfig(targetName) {
206
+ const parsed = this.options.readConfigDocument();
207
+ const currentConfig = parsed?.mcpServers?.[targetName];
208
+ if (!currentConfig) {
209
+ throw new AdminConfiguredServerNotFoundError(targetName);
210
+ }
211
+ return currentConfig;
212
+ }
213
+ async previewConfiguredServerEditResult(input, currentConfig, serverDefaults, requireCriticalConnectivity = false) {
214
+ const normalizedEdit = normalizeEditDraft(input.edit);
215
+ const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
216
+ const secretValidation = validateSecretEditCapabilities(normalizedEdit.edit, currentReadModel.secretInputs);
217
+ const transportApplicabilityValidation = validateTransportFieldApplicability(currentConfig, normalizedEdit.edit);
218
+ const overrideValidation = validateTransportOverrideClears(currentConfig, normalizedEdit.edit);
219
+ const applicableEdit = filterApplicableSecretEdits(normalizedEdit.edit, currentReadModel.secretInputs);
220
+ const proposedTargetName = applicableEdit.id?.trim() || input.targetName;
221
+ const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults);
222
+ const proposedReadModel = createConfiguredServerReadModel(proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig));
223
+ const proposedTransportType = configuredTransportType(proposedConfig);
224
+ const serverValidation = validatePreviewServerConfig(proposedConfig);
225
+ const validation = mergeValidation(mergeValidation(mergeValidation(mergeValidation(normalizedEdit.validation, secretValidation), transportApplicabilityValidation), overrideValidation), serverValidation);
226
+ const diff = createPreviewDiff(currentReadModel, proposedReadModel, applicableEdit);
227
+ appendClearedOverrideDiff(diff, currentReadModel, proposedReadModel, applicableEdit);
228
+ const changed = validation.status === 'valid' && diff.length > 0;
229
+ const endpointChangedWithPreservedSecrets = endpointAuthorityChanged(currentConfig, proposedConfig) &&
230
+ hasPreservedSecretInputs(currentReadModel.secretInputs.filter((input) => !proposedTransportType || transportFieldAppliesToType(input.fieldPath[0], proposedTransportType)), applicableEdit);
231
+ return {
232
+ targetName: input.targetName,
233
+ proposedTargetName,
234
+ previewFingerprint: previewFingerprint({
235
+ targetName: input.targetName,
236
+ sourceFingerprint: fingerprintConfiguredServerTarget(currentConfig),
237
+ globalConfigFingerprint: fingerprintConfiguredServerDefaults(serverDefaults),
238
+ edit: normalizedEdit.edit,
239
+ current: currentReadModel,
240
+ proposed: proposedReadModel,
241
+ diff,
242
+ validation,
243
+ }),
244
+ validation,
245
+ diff,
246
+ configChange: previewConfigChange(input.targetName, changed),
247
+ connectivityCheck: await this.previewConnectivityCheck({
248
+ targetName: proposedTargetName,
249
+ serverConfig: proposedConfig,
250
+ enabled: proposedReadModel.enabled,
251
+ validationStatus: validation.status,
252
+ connectionCriticalChanged: diff.some((entry) => entry.riskFlags.includes('connection_critical')),
253
+ force: input.connectivityCheck === 'manual' ||
254
+ (requireCriticalConnectivity && diff.some((entry) => entry.riskFlags.includes('connection_critical'))),
255
+ endpointChangedWithPreservedSecrets,
256
+ }),
257
+ };
258
+ }
259
+ async previewConnectivityCheck(input) {
260
+ if (!input.enabled) {
261
+ return { status: 'skipped', reason: 'target_disabled' };
262
+ }
263
+ if (input.validationStatus === 'invalid') {
264
+ return { status: 'skipped', reason: 'validation_failed' };
265
+ }
266
+ if (!input.connectionCriticalChanged && !input.force) {
267
+ return { status: 'skipped', reason: 'connection_critical_fields_unchanged' };
268
+ }
269
+ if (input.endpointChangedWithPreservedSecrets && !input.force) {
270
+ return { status: 'skipped', reason: 'endpoint_changed_with_preserved_secrets' };
271
+ }
272
+ if (wouldUseLocalStdioTransport(input.serverConfig)) {
273
+ return { status: 'skipped', reason: 'local_stdio_transport' };
274
+ }
275
+ if (!this.options.checkConnectivity) {
276
+ return { status: 'skipped', reason: 'checker_unavailable' };
277
+ }
278
+ return this.options.checkConnectivity({
279
+ targetName: input.targetName,
280
+ serverConfig: input.serverConfig,
281
+ });
282
+ }
283
+ }
284
+ function assertSuccessfulConfigChange(configChange) {
285
+ if (configChange.reload.status === 'failed') {
286
+ throw new Error(`Config reload observation failed: ${configChange.reload.error ?? 'unknown reload failure'}`);
287
+ }
288
+ if (configChange.status === 'failed') {
289
+ throw new Error(configChange.error ?? 'Config change failed');
290
+ }
291
+ if (configChange.status === 'not_found') {
292
+ throw new Error(`Configured server target '${configChange.target.name}' was not found`);
293
+ }
294
+ if (configChange.status === 'template_conflict') {
295
+ throw new Error(configChange.error ?? `Configured server target '${configChange.target.name}' is unsupported`);
296
+ }
297
+ }
298
+ export class AdminConfiguredServerApplyError extends Error {
299
+ code;
300
+ constructor(code) {
301
+ super(code);
302
+ this.code = code;
303
+ this.name = 'AdminConfiguredServerApplyError';
304
+ }
305
+ }
306
+ function assertSuccessfulConfiguredServerEdit(configChange) {
307
+ switch (configChange.status) {
308
+ case 'changed':
309
+ return;
310
+ case 'source_conflict':
311
+ throw new AdminConfiguredServerApplyError('configured_server_stale_preview');
312
+ case 'destination_conflict':
313
+ case 'template_conflict':
314
+ throw new AdminConfiguredServerApplyError('configured_server_destination_conflict');
315
+ case 'not_found':
316
+ throw new AdminConfiguredServerApplyError('configured_server_not_found');
317
+ case 'unchanged':
318
+ throw new AdminConfiguredServerApplyError('configured_server_edit_unchanged');
319
+ default:
320
+ throw new AdminConfiguredServerApplyError('configured_server_apply_failed');
321
+ }
322
+ }
323
+ function sanitizeApplyConfirmationFacts(facts) {
324
+ if (!facts)
325
+ return undefined;
326
+ const allowed = ['previewConfirmed', 'targetNameConfirmed', 'secretChangeConfirmed', 'connectionCriticalConfirmed'];
327
+ allowed.push('connectivityFailureOverrideConfirmed');
328
+ const sanitized = Object.fromEntries(allowed.filter((key) => key in facts).map((key) => [key, facts[key]]));
329
+ return Object.keys(sanitized).length > 0 ? sanitized : undefined;
330
+ }
331
+ function mutationOutcome(enabled, changed) {
332
+ if (changed) {
333
+ return enabled ? 'enabled' : 'disabled';
334
+ }
335
+ return enabled ? 'already_enabled' : 'already_disabled';
336
+ }
337
+ function normalizeEditDraft(value) {
338
+ const errors = [];
339
+ const edit = {};
340
+ const supportedKeys = new Set(['id', 'enabled', 'tags', 'transport', 'secrets', 'clearTransportOverrides']);
341
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
342
+ return {
343
+ edit,
344
+ validation: {
345
+ status: 'invalid',
346
+ errors: [
347
+ {
348
+ fieldPath: [],
349
+ code: 'invalid_edit',
350
+ message: 'Edit must be an object.',
351
+ },
352
+ ],
353
+ },
354
+ };
355
+ }
356
+ const record = value;
357
+ for (const key of Object.keys(record)) {
358
+ if (!supportedKeys.has(key)) {
359
+ errors.push({
360
+ fieldPath: [key],
361
+ code: 'unsupported_edit_field',
362
+ message: 'Edit field is not supported by the normalized configured-server edit contract.',
363
+ });
364
+ }
365
+ }
366
+ if (record.id !== undefined) {
367
+ if (typeof record.id === 'string') {
368
+ edit.id = record.id;
369
+ }
370
+ else {
371
+ errors.push({
372
+ fieldPath: ['id'],
373
+ code: 'invalid_target_id',
374
+ message: 'Target ID must be a string.',
375
+ });
376
+ }
377
+ }
378
+ if (record.enabled !== undefined) {
379
+ if (typeof record.enabled === 'boolean') {
380
+ edit.enabled = record.enabled;
381
+ }
382
+ else {
383
+ errors.push({
384
+ fieldPath: ['enabled'],
385
+ code: 'invalid_enabled',
386
+ message: 'Enabled must be true or false.',
387
+ });
388
+ }
389
+ }
390
+ if (record.tags !== undefined) {
391
+ if (Array.isArray(record.tags) && record.tags.every((tag) => typeof tag === 'string')) {
392
+ edit.tags = record.tags;
393
+ }
394
+ else {
395
+ errors.push({
396
+ fieldPath: ['tags'],
397
+ code: 'invalid_tags',
398
+ message: 'Tags must be a list of strings.',
399
+ });
400
+ }
401
+ }
402
+ if (record.transport !== undefined) {
403
+ if (record.transport && typeof record.transport === 'object' && !Array.isArray(record.transport)) {
404
+ edit.transport = normalizeTransportEditDraft(record.transport, errors);
405
+ }
406
+ else {
407
+ errors.push({
408
+ fieldPath: ['transport'],
409
+ code: 'invalid_transport',
410
+ message: 'Transport edits must be an object.',
411
+ });
412
+ }
413
+ }
414
+ if (record.secrets !== undefined) {
415
+ if (Array.isArray(record.secrets)) {
416
+ edit.secrets = record.secrets.flatMap((entry, index) => normalizeSecretEditDraft(entry, index, errors));
417
+ }
418
+ else {
419
+ errors.push({
420
+ fieldPath: ['secrets'],
421
+ code: 'invalid_secret_actions',
422
+ message: 'Secret actions must be a list.',
423
+ });
424
+ }
425
+ }
426
+ if (record.clearTransportOverrides !== undefined) {
427
+ const allowedKeys = new Set(GLOBAL_TRANSPORT_CONFIG_KEYS);
428
+ if (Array.isArray(record.clearTransportOverrides) &&
429
+ record.clearTransportOverrides.every((key) => typeof key === 'string' && allowedKeys.has(key))) {
430
+ edit.clearTransportOverrides = Array.from(new Set(record.clearTransportOverrides));
431
+ }
432
+ else {
433
+ errors.push({
434
+ fieldPath: ['clearTransportOverrides'],
435
+ code: 'invalid_transport_override_clear',
436
+ message: 'Transport overrides must be a list of supported global transport field names.',
437
+ });
438
+ }
439
+ }
440
+ return {
441
+ edit,
442
+ validation: { status: errors.length === 0 ? 'valid' : 'invalid', errors },
443
+ };
444
+ }
445
+ function normalizeTransportEditDraft(record, errors) {
446
+ const transport = {};
447
+ for (const [key, value] of Object.entries(record)) {
448
+ if (!isSafeFieldPathSegment(key)) {
449
+ errors.push({
450
+ fieldPath: ['transport', key],
451
+ code: 'invalid_transport_field_path',
452
+ message: 'Transport field path contains a reserved segment.',
453
+ });
454
+ continue;
455
+ }
456
+ if (isSecretCapableRawTransportEdit(key, value)) {
457
+ errors.push({
458
+ fieldPath: ['transport', key],
459
+ code: 'secret_transport_edit_requires_secret_action',
460
+ message: 'Secret-capable transport fields must use explicit secret actions.',
461
+ });
462
+ continue;
463
+ }
464
+ transport[key] = value;
465
+ }
466
+ return transport;
467
+ }
468
+ function isSecretCapableRawTransportEdit(key, value) {
469
+ if (key === 'url') {
470
+ return isSecretCapableUrlEdit(value);
471
+ }
472
+ if (key === 'headers' || key === 'env') {
473
+ return true;
474
+ }
475
+ if (key === 'oauth' && value && typeof value === 'object' && !Array.isArray(value)) {
476
+ return containsStringOrSecretLikeField(value);
477
+ }
478
+ if (key === 'args' && Array.isArray(value)) {
479
+ return argsContainSecretMaterial(value);
480
+ }
481
+ if (isSecretLikeKey(key) || containsSecretLikeField(value)) {
482
+ return true;
483
+ }
484
+ return false;
485
+ }
486
+ function argsContainSecretMaterial(value) {
487
+ return value.some((entry, index) => {
488
+ if (typeof entry !== 'string') {
489
+ return containsSecretLikeField(entry);
490
+ }
491
+ const previous = value[index - 1];
492
+ return Boolean(secretAssignmentArg(entry) ||
493
+ secretFlagArg(entry) ||
494
+ containsSecretLikeString(entry) ||
495
+ (typeof previous === 'string' && secretCarrierArg(previous) && containsSecretLikeString(entry)));
496
+ });
497
+ }
498
+ function secretCarrierArg(value) {
499
+ return /^-{1,2}(header|h|env|e)$/iu.test(value);
500
+ }
501
+ function containsSecretLikeString(value) {
502
+ return (/\b(Bearer|Basic)\s+[^\s"',;]+/iu.test(value) ||
503
+ /\b(?:token|secret|password|auth|api[_-]?key|credential)\b\s*[:=]/iu.test(value));
504
+ }
505
+ function containsSecretLikeField(value) {
506
+ if (Array.isArray(value)) {
507
+ return value.some(containsSecretLikeField);
508
+ }
509
+ if (typeof value === 'string') {
510
+ return containsSecretLikeString(value);
511
+ }
512
+ if (!value || typeof value !== 'object') {
513
+ return false;
514
+ }
515
+ return Object.entries(value).some(([nestedKey, nestedValue]) => isSecretLikeKey(nestedKey) || containsSecretLikeField(nestedValue));
516
+ }
517
+ function containsStringOrSecretLikeField(value) {
518
+ if (Array.isArray(value)) {
519
+ return value.some(containsStringOrSecretLikeField);
520
+ }
521
+ if (typeof value === 'string') {
522
+ return true;
523
+ }
524
+ if (!value || typeof value !== 'object') {
525
+ return false;
526
+ }
527
+ return Object.entries(value).some(([nestedKey, nestedValue]) => isSecretLikeKey(nestedKey) || containsStringOrSecretLikeField(nestedValue));
528
+ }
529
+ function isSecretCapableUrlEdit(value) {
530
+ if (typeof value !== 'string') {
531
+ return false;
532
+ }
533
+ try {
534
+ const url = new URL(value);
535
+ if (url.username || url.password) {
536
+ return true;
537
+ }
538
+ for (const [key, paramValue] of url.searchParams.entries()) {
539
+ if (isSecretLikeKey(key) || containsSecretLikeString(paramValue)) {
540
+ return true;
541
+ }
542
+ }
543
+ return false;
544
+ }
545
+ catch {
546
+ if (/^[a-z][a-z0-9+.-]*:\/\/[^@/?#]+@/iu.test(value)) {
547
+ return true;
548
+ }
549
+ return /[?&][^=]*(?:token|secret|password|auth|key|credential)[^=]*=[^&]+/iu.test(value);
550
+ }
551
+ }
552
+ function normalizeSecretEditDraft(value, index, errors) {
553
+ const basePath = ['secrets', String(index)];
554
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
555
+ errors.push({
556
+ fieldPath: basePath,
557
+ code: 'invalid_secret_action',
558
+ message: 'Secret action must be an object.',
559
+ });
560
+ return [];
561
+ }
562
+ const record = value;
563
+ if (!Array.isArray(record.fieldPath) || !record.fieldPath.every((segment) => typeof segment === 'string')) {
564
+ errors.push({
565
+ fieldPath: [...basePath, 'fieldPath'],
566
+ code: 'invalid_secret_field_path',
567
+ message: 'Secret field path must be a list of strings.',
568
+ });
569
+ return [];
570
+ }
571
+ if (!isValidFieldPath(record.fieldPath)) {
572
+ errors.push({
573
+ fieldPath: [...basePath, 'fieldPath'],
574
+ code: 'invalid_secret_field_path',
575
+ message: 'Secret field path contains a reserved segment.',
576
+ });
577
+ return [];
578
+ }
579
+ if (record.action !== 'preserve' && record.action !== 'replace' && record.action !== 'clear') {
580
+ errors.push({
581
+ fieldPath: [...basePath, 'action'],
582
+ code: 'invalid_secret_action',
583
+ message: 'Secret action must be preserve, replace, or clear.',
584
+ });
585
+ return [];
586
+ }
587
+ const hasReplacement = record.replacement !== undefined;
588
+ const replacement = normalizeSecretReplacement(record.replacement, basePath, errors);
589
+ if (record.action === 'replace' && !hasReplacement) {
590
+ errors.push({
591
+ fieldPath: [...basePath, 'replacement'],
592
+ code: 'missing_secret_replacement',
593
+ message: 'Replace actions require an explicit replacement.',
594
+ });
595
+ }
596
+ if (record.action !== 'replace' && hasReplacement) {
597
+ errors.push({
598
+ fieldPath: [...basePath, 'replacement'],
599
+ code: 'unexpected_secret_replacement',
600
+ message: 'Only replace actions may include a replacement.',
601
+ });
602
+ }
603
+ return [
604
+ {
605
+ fieldPath: record.fieldPath,
606
+ action: record.action,
607
+ ...(record.action === 'replace' && replacement ? { replacement } : {}),
608
+ },
609
+ ];
610
+ }
611
+ function normalizeSecretReplacement(value, basePath, errors) {
612
+ if (value === undefined) {
613
+ return undefined;
614
+ }
615
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
616
+ errors.push({
617
+ fieldPath: [...basePath, 'replacement'],
618
+ code: 'invalid_secret_replacement',
619
+ message: 'Secret replacement must be an object.',
620
+ });
621
+ return undefined;
622
+ }
623
+ const record = value;
624
+ if (record.kind !== 'environmentReference' && record.kind !== 'inlineSecret') {
625
+ errors.push({
626
+ fieldPath: [...basePath, 'replacement', 'kind'],
627
+ code: 'invalid_secret_replacement_kind',
628
+ message: 'Secret replacement kind must be environmentReference or inlineSecret.',
629
+ });
630
+ return undefined;
631
+ }
632
+ if (typeof record.value !== 'string') {
633
+ errors.push({
634
+ fieldPath: [...basePath, 'replacement', 'value'],
635
+ code: 'invalid_secret_replacement_value',
636
+ message: 'Secret replacement value must be a string.',
637
+ });
638
+ return undefined;
639
+ }
640
+ if (record.kind === 'environmentReference' && !isEnvironmentReferenceInput(record.value)) {
641
+ errors.push({
642
+ fieldPath: [...basePath, 'replacement', 'value'],
643
+ code: 'invalid_environment_reference',
644
+ message: 'Environment reference must be an environment variable name or substitution expression.',
645
+ });
646
+ return undefined;
647
+ }
648
+ if (record.kind === 'inlineSecret' && record.value.length === 0) {
649
+ errors.push({
650
+ fieldPath: [...basePath, 'replacement', 'value'],
651
+ code: 'empty_inline_secret',
652
+ message: 'Inline secret replacement cannot be empty; use clear to remove a secret.',
653
+ });
654
+ return undefined;
655
+ }
656
+ return {
657
+ kind: record.kind,
658
+ value: record.value,
659
+ };
660
+ }
661
+ function validateSecretEditCapabilities(edit, secretInputs) {
662
+ const inputs = secretInputMap(secretInputs);
663
+ const errors = [];
664
+ for (const [index, secret] of (edit.secrets ?? []).entries()) {
665
+ const basePath = ['secrets', String(index)];
666
+ const input = resolveSecretInput(secret.fieldPath, inputs);
667
+ if (!input) {
668
+ errors.push({
669
+ fieldPath: [...basePath, 'fieldPath'],
670
+ code: 'unsupported_secret_field',
671
+ message: 'Secret action must target a secret-capable field.',
672
+ });
673
+ continue;
674
+ }
675
+ if (!input.allowedActions.includes(secret.action)) {
676
+ errors.push({
677
+ fieldPath: [...basePath, 'action'],
678
+ code: 'unsupported_secret_action',
679
+ message: 'Secret action is not supported for this field.',
680
+ });
681
+ }
682
+ }
683
+ return {
684
+ status: errors.length === 0 ? 'valid' : 'invalid',
685
+ errors,
686
+ };
687
+ }
688
+ function filterApplicableSecretEdits(edit, secretInputs) {
689
+ if (!edit.secrets) {
690
+ return edit;
691
+ }
692
+ const inputs = secretInputMap(secretInputs);
693
+ return {
694
+ ...edit,
695
+ secrets: edit.secrets.filter((secret) => {
696
+ const input = resolveSecretInput(secret.fieldPath, inputs);
697
+ return Boolean(input &&
698
+ input.allowedActions.includes(secret.action) &&
699
+ (secret.action === 'replace' ? secret.replacement : !secret.replacement));
700
+ }),
701
+ };
702
+ }
703
+ function secretInputMap(secretInputs) {
704
+ return new Map(secretInputs.map((input) => [fieldKey(input.fieldPath), input]));
705
+ }
706
+ function hasPreservedSecretInputs(secretInputs, edit) {
707
+ if (secretInputs.length === 0) {
708
+ return false;
709
+ }
710
+ const explicitActions = new Map((edit.secrets ?? []).map((secret) => [fieldKey(secret.fieldPath), secret.action]));
711
+ return secretInputs.some((input) => {
712
+ const action = explicitActions.get(fieldKey(input.fieldPath));
713
+ return action !== 'replace' && action !== 'clear';
714
+ });
715
+ }
716
+ function endpointAuthorityChanged(currentConfig, proposedConfig) {
717
+ const currentAuthority = endpointAuthority(currentConfig);
718
+ const proposedAuthority = endpointAuthority(proposedConfig);
719
+ if (currentAuthority || proposedAuthority) {
720
+ return currentAuthority !== proposedAuthority;
721
+ }
722
+ const currentUrl = typeof currentConfig.url === 'string' ? currentConfig.url : undefined;
723
+ const proposedUrl = typeof proposedConfig.url === 'string' ? proposedConfig.url : undefined;
724
+ return currentUrl !== proposedUrl && Boolean(currentUrl || proposedUrl);
725
+ }
726
+ function endpointAuthority(serverConfig) {
727
+ if (typeof serverConfig.url !== 'string') {
728
+ return undefined;
729
+ }
730
+ try {
731
+ const url = new URL(serverConfig.url);
732
+ return `${url.protocol}//${url.host}`;
733
+ }
734
+ catch {
735
+ return undefined;
736
+ }
737
+ }
738
+ function resolveSecretInput(fieldPath, inputs) {
739
+ return inputs.get(fieldKey(fieldPath)) ?? potentialSecretInput(fieldPath);
740
+ }
741
+ function potentialSecretInput(fieldPath) {
742
+ if (!isValidFieldPath(fieldPath)) {
743
+ return undefined;
744
+ }
745
+ if (fieldPath[0] === 'headers' && fieldPath.length === 2) {
746
+ return secretInput(fieldPath, fieldPath[1]);
747
+ }
748
+ if (fieldPath[0] === 'env' && fieldPath.length === 2) {
749
+ return secretInput(fieldPath, fieldPath[1]);
750
+ }
751
+ if (fieldPath[0] === 'oauth' && fieldPath.length === 2) {
752
+ return secretInput(fieldPath, fieldPath[1]);
753
+ }
754
+ if (fieldPath[0] === 'url') {
755
+ if ((fieldPath[1] === 'username' || fieldPath[1] === 'password') && fieldPath.length === 2) {
756
+ return secretInput(fieldPath, `url.${fieldPath[1]}`);
757
+ }
758
+ if (fieldPath[1] === 'query' && fieldPath.length === 3 && isSecretLikeKey(fieldPath[2])) {
759
+ return secretInput(fieldPath, `url.query.${fieldPath[2]}`);
760
+ }
761
+ }
762
+ return undefined;
763
+ }
764
+ function applyEditDraft(currentConfig, edit, serverDefaults) {
765
+ const nextConfig = cloneServerConfig(currentConfig);
766
+ for (const key of edit.clearTransportOverrides ?? []) {
767
+ delete nextConfig[key];
768
+ }
769
+ if (typeof edit.enabled === 'boolean') {
770
+ if (edit.enabled) {
771
+ delete nextConfig.disabled;
772
+ }
773
+ else {
774
+ nextConfig.disabled = true;
775
+ }
776
+ }
777
+ if (Array.isArray(edit.tags)) {
778
+ nextConfig.tags = edit.tags.filter((tag) => typeof tag === 'string');
779
+ }
780
+ if (edit.transport && typeof edit.transport === 'object') {
781
+ const currentType = configuredTransportType(currentConfig);
782
+ const proposedType = configuredTransportType({ ...currentConfig, ...edit.transport });
783
+ if (currentType && proposedType && currentType !== proposedType) {
784
+ for (const definition of TRANSPORT_FIELD_DEFINITIONS) {
785
+ if (definition.applicableTransportTypes && !definition.applicableTransportTypes.includes(proposedType)) {
786
+ delete nextConfig[definition.key];
787
+ }
788
+ }
789
+ }
790
+ for (const [key, value] of Object.entries(edit.transport)) {
791
+ if (value === undefined ||
792
+ !isSafeFieldPathSegment(key) ||
793
+ (proposedType && !transportFieldAppliesToType(key, proposedType))) {
794
+ continue;
795
+ }
796
+ nextConfig[key] = cloneUnknown(value);
797
+ }
798
+ }
799
+ for (const secret of edit.secrets ?? []) {
800
+ seedWholeFieldSecretOverride(nextConfig, serverDefaults, secret);
801
+ applySecretEdit(nextConfig, secret);
802
+ }
803
+ return nextConfig;
804
+ }
805
+ function seedWholeFieldSecretOverride(serverConfig, serverDefaults, secret) {
806
+ if (secret.action !== 'replace')
807
+ return;
808
+ const key = secret.fieldPath[0];
809
+ if (key !== 'headers' && key !== 'oauth')
810
+ return;
811
+ if (Object.prototype.hasOwnProperty.call(serverConfig, key))
812
+ return;
813
+ const inherited = serverDefaults?.[key];
814
+ if (!inherited || typeof inherited !== 'object' || Array.isArray(inherited))
815
+ return;
816
+ serverConfig[key] = cloneUnknown(inherited);
817
+ }
818
+ function cloneServerConfig(serverConfig) {
819
+ return cloneUnknown(serverConfig);
820
+ }
821
+ function validateTransportFieldApplicability(currentConfig, edit) {
822
+ const transport = edit.transport;
823
+ if (!transport)
824
+ return { status: 'valid', errors: [] };
825
+ const proposedType = configuredTransportType({ ...currentConfig, ...transport });
826
+ if (!proposedType)
827
+ return { status: 'valid', errors: [] };
828
+ const errors = [];
829
+ for (const key of Object.keys(transport)) {
830
+ if (transportFieldAppliesToType(key, proposedType))
831
+ continue;
832
+ errors.push({
833
+ fieldPath: ['transport', key],
834
+ code: 'transport_field_not_applicable',
835
+ message: `${TRANSPORT_FIELD_DEFINITION_BY_KEY.get(key)?.label ?? labelFromPath([key])} does not apply to ${proposedType} transports.`,
836
+ });
837
+ }
838
+ return { status: errors.length > 0 ? 'invalid' : 'valid', errors };
839
+ }
840
+ function validateTransportOverrideClears(currentConfig, edit) {
841
+ const secretEditedKeys = new Set((edit.secrets ?? []).map((secret) => secret.fieldPath[0]));
842
+ const errors = (edit.clearTransportOverrides ?? []).flatMap((key) => {
843
+ if (!Object.prototype.hasOwnProperty.call(currentConfig, key)) {
844
+ return [
845
+ {
846
+ fieldPath: ['clearTransportOverrides', key],
847
+ code: 'transport_override_not_present',
848
+ message: `${TRANSPORT_FIELD_DEFINITION_BY_KEY.get(key)?.label ?? labelFromPath([key])} is not overridden by this server.`,
849
+ },
850
+ ];
851
+ }
852
+ if (Object.prototype.hasOwnProperty.call(edit.transport ?? {}, key) || secretEditedKeys.has(key)) {
853
+ return [
854
+ {
855
+ fieldPath: ['clearTransportOverrides', key],
856
+ code: 'transport_override_conflict',
857
+ message: 'A transport override cannot be cleared and edited in the same request.',
858
+ },
859
+ ];
860
+ }
861
+ return [];
862
+ });
863
+ return { status: errors.length > 0 ? 'invalid' : 'valid', errors };
864
+ }
865
+ function configuredTransportType(config) {
866
+ if (config.type === 'stdio' || config.type === 'http' || config.type === 'sse' || config.type === 'streamableHttp') {
867
+ return config.type;
868
+ }
869
+ if (typeof config.command === 'string')
870
+ return 'stdio';
871
+ if (typeof config.url === 'string')
872
+ return 'http';
873
+ return undefined;
874
+ }
875
+ function transportFieldApplicability(key) {
876
+ return TRANSPORT_FIELD_DEFINITION_BY_KEY.get(key)?.applicableTransportTypes;
877
+ }
878
+ function transportFieldAppliesToType(key, transportType) {
879
+ const applicableTypes = transportFieldApplicability(key);
880
+ return !applicableTypes || applicableTypes.includes(transportType);
881
+ }
882
+ function cloneUnknown(value) {
883
+ return JSON.parse(JSON.stringify(value));
884
+ }
885
+ function applySecretEdit(serverConfig, secret) {
886
+ if (!isValidFieldPath(secret.fieldPath)) {
887
+ return;
888
+ }
889
+ if (secret.action === 'preserve') {
890
+ return;
891
+ }
892
+ if (applyVirtualSecretEdit(serverConfig, secret)) {
893
+ return;
894
+ }
895
+ if (secret.action === 'clear') {
896
+ deleteNestedValue(serverConfig, secret.fieldPath);
897
+ return;
898
+ }
899
+ if (secret.action === 'replace' && secret.replacement) {
900
+ setNestedValue(serverConfig, secret.fieldPath, replacementValue(secret.replacement));
901
+ }
902
+ }
903
+ function applyVirtualSecretEdit(serverConfig, secret) {
904
+ if (secret.fieldPath[0] === 'url') {
905
+ return applyUrlSecretEdit(serverConfig, secret);
906
+ }
907
+ if (secret.fieldPath[0] === 'args') {
908
+ return applyArgsSecretEdit(serverConfig, secret);
909
+ }
910
+ if (secret.fieldPath[0] === 'env') {
911
+ return applyEnvSecretEdit(serverConfig, secret);
912
+ }
913
+ return false;
914
+ }
915
+ function applyUrlSecretEdit(serverConfig, secret) {
916
+ if (typeof serverConfig.url !== 'string') {
917
+ return true;
918
+ }
919
+ try {
920
+ new URL(serverConfig.url);
921
+ }
922
+ catch {
923
+ return true;
924
+ }
925
+ const value = secret.action === 'replace' && secret.replacement ? replacementValue(secret.replacement) : undefined;
926
+ const [, scope, key] = secret.fieldPath;
927
+ if (scope === 'username') {
928
+ serverConfig.url = replaceRawUrlUserInfo(serverConfig.url, 'username', value);
929
+ return true;
930
+ }
931
+ if (scope === 'password') {
932
+ serverConfig.url = replaceRawUrlUserInfo(serverConfig.url, 'password', value);
933
+ return true;
934
+ }
935
+ if (scope === 'query' && key) {
936
+ serverConfig.url = replaceRawUrlQueryParam(serverConfig.url, key, value);
937
+ return true;
938
+ }
939
+ return true;
940
+ }
941
+ function applyEnvSecretEdit(serverConfig, secret) {
942
+ if (!Array.isArray(serverConfig.env) || secret.fieldPath.length !== 2) {
943
+ return false;
944
+ }
945
+ const key = secret.fieldPath[1];
946
+ const entryIndex = serverConfig.env.findIndex((entry) => typeof entry === 'string' && envArrayEntryKey(entry) === key);
947
+ if (secret.action === 'clear') {
948
+ if (entryIndex >= 0) {
949
+ serverConfig.env.splice(entryIndex, 1);
950
+ }
951
+ return true;
952
+ }
953
+ if (secret.action === 'replace' && secret.replacement) {
954
+ const value = replacementValue(secret.replacement);
955
+ const nextEntry = `${key}=${value}`;
956
+ if (entryIndex >= 0) {
957
+ serverConfig.env[entryIndex] = nextEntry;
958
+ }
959
+ else {
960
+ serverConfig.env.push(nextEntry);
961
+ }
962
+ }
963
+ return true;
964
+ }
965
+ function envArrayEntryKey(entry) {
966
+ return entry.includes('=') ? entry.split('=', 1)[0] : entry;
967
+ }
968
+ function replaceRawUrlUserInfo(input, scope, value) {
969
+ const match = input.match(/^([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^/?#@]*@)?(.*)$/u);
970
+ if (!match) {
971
+ return input;
972
+ }
973
+ const prefix = match[1];
974
+ const rawUserInfo = match[2]?.slice(0, -1) ?? '';
975
+ const rest = match[3];
976
+ const [currentUsername = '', currentPassword = ''] = rawUserInfo.split(':');
977
+ const username = scope === 'username' ? (value ?? '') : currentUsername;
978
+ const password = scope === 'password' ? (value ?? '') : currentPassword;
979
+ const userInfo = username || password ? `${username}${password ? `:${password}` : ''}@` : '';
980
+ return `${prefix}${userInfo}${rest}`;
981
+ }
982
+ function replaceRawUrlQueryParam(input, key, value) {
983
+ const [withoutFragment, fragment = ''] = input.split('#', 2);
984
+ const [base, query = ''] = withoutFragment.split('?', 2);
985
+ const params = query ? query.split('&').filter((entry) => entry.length > 0) : [];
986
+ const keyPrefix = `${key}=`;
987
+ let replaced = false;
988
+ const nextParams = params.flatMap((entry) => {
989
+ const entryKey = entry.split('=', 1)[0];
990
+ if (entryKey !== key) {
991
+ return [entry];
992
+ }
993
+ replaced = true;
994
+ return value === undefined ? [] : [`${keyPrefix}${value}`];
995
+ });
996
+ if (!replaced && value !== undefined) {
997
+ nextParams.push(`${keyPrefix}${value}`);
998
+ }
999
+ const querySuffix = nextParams.length > 0 ? `?${nextParams.join('&')}` : '';
1000
+ const fragmentSuffix = fragment ? `#${fragment}` : '';
1001
+ return `${base}${querySuffix}${fragmentSuffix}`;
1002
+ }
1003
+ function applyArgsSecretEdit(serverConfig, secret) {
1004
+ if (!Array.isArray(serverConfig.args) || secret.fieldPath.length !== 2) {
1005
+ return true;
1006
+ }
1007
+ const index = Number(secret.fieldPath[1]);
1008
+ if (!Number.isInteger(index) || index < 0 || index >= serverConfig.args.length) {
1009
+ return true;
1010
+ }
1011
+ const current = serverConfig.args[index];
1012
+ if (typeof current !== 'string') {
1013
+ return true;
1014
+ }
1015
+ if (secret.action === 'clear') {
1016
+ serverConfig.args.splice(index, 1);
1017
+ return true;
1018
+ }
1019
+ if (secret.action === 'replace' && secret.replacement) {
1020
+ const value = replacementValue(secret.replacement);
1021
+ const assignment = secretAssignmentArg(current);
1022
+ serverConfig.args[index] = assignment ? `${assignment.prefix}${value}` : value;
1023
+ }
1024
+ return true;
1025
+ }
1026
+ function replacementValue(replacement) {
1027
+ if (replacement.kind === 'environmentReference') {
1028
+ return normalizeEnvironmentReference(replacement.value);
1029
+ }
1030
+ return replacement.value;
1031
+ }
1032
+ function normalizeEnvironmentReference(value) {
1033
+ const trimmed = value.trim();
1034
+ if (!trimmed) {
1035
+ return '';
1036
+ }
1037
+ if (/^\$\{[A-Z_][A-Z0-9_]*\}$/u.test(trimmed) || /^\$[A-Z_][A-Z0-9_]*$/u.test(trimmed)) {
1038
+ return trimmed;
1039
+ }
1040
+ return `\${${trimmed}}`;
1041
+ }
1042
+ function isEnvironmentReferenceInput(value) {
1043
+ const trimmed = value.trim();
1044
+ return (/^[A-Z_][A-Z0-9_]*$/u.test(trimmed) ||
1045
+ /^\$[A-Z_][A-Z0-9_]*$/u.test(trimmed) ||
1046
+ /^\$\{[A-Z_][A-Z0-9_]*\}$/u.test(trimmed));
1047
+ }
1048
+ function isValidFieldPath(fieldPath) {
1049
+ return fieldPath.length > 0 && fieldPath.every(isSafeFieldPathSegment);
1050
+ }
1051
+ function isSafeFieldPathSegment(segment) {
1052
+ return segment.length > 0 && segment !== '__proto__' && segment !== 'prototype' && segment !== 'constructor';
1053
+ }
1054
+ function setNestedValue(target, fieldPath, value) {
1055
+ let cursor = target;
1056
+ for (const segment of fieldPath.slice(0, -1)) {
1057
+ const existing = cursor[segment];
1058
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
1059
+ cursor[segment] = {};
1060
+ }
1061
+ cursor = cursor[segment];
1062
+ }
1063
+ cursor[fieldPath[fieldPath.length - 1]] = value;
1064
+ }
1065
+ function deleteNestedValue(target, fieldPath) {
1066
+ let cursor = target;
1067
+ for (const segment of fieldPath.slice(0, -1)) {
1068
+ const existing = cursor[segment];
1069
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
1070
+ return;
1071
+ }
1072
+ cursor = existing;
1073
+ }
1074
+ delete cursor[fieldPath[fieldPath.length - 1]];
1075
+ }
1076
+ function validatePreviewServerConfig(serverConfig) {
1077
+ const errors = [];
1078
+ const type = typeof serverConfig.type === 'string' ? serverConfig.type : undefined;
1079
+ const url = typeof serverConfig.url === 'string' ? serverConfig.url : undefined;
1080
+ const command = typeof serverConfig.command === 'string' ? serverConfig.command : undefined;
1081
+ const schemaResult = previewTransportConfigSchema.safeParse(serverConfig);
1082
+ if (!schemaResult.success) {
1083
+ for (const issue of schemaResult.error.issues) {
1084
+ if ((issue.path[0] === 'type' && typeof serverConfig.type === 'string') ||
1085
+ (issue.path[0] === 'url' && typeof serverConfig.url === 'string') ||
1086
+ issue.path[0] === 'args') {
1087
+ continue;
1088
+ }
1089
+ errors.push({
1090
+ fieldPath: ['transport', ...issue.path.map(String)],
1091
+ code: 'invalid_transport_field',
1092
+ message: issue.message,
1093
+ });
1094
+ }
1095
+ }
1096
+ if (type === 'stdio' && !command) {
1097
+ errors.push({
1098
+ fieldPath: ['transport', 'command'],
1099
+ code: 'missing_stdio_command',
1100
+ message: 'Command is required for stdio servers.',
1101
+ });
1102
+ }
1103
+ if ((type === 'http' || type === 'sse' || type === 'streamableHttp') && !url) {
1104
+ errors.push({
1105
+ fieldPath: ['transport', 'url'],
1106
+ code: 'missing_transport_url',
1107
+ message: `URL is required for ${type} servers.`,
1108
+ });
1109
+ }
1110
+ if (type && !['stdio', 'http', 'sse', 'streamableHttp'].includes(type)) {
1111
+ errors.push({
1112
+ fieldPath: ['transport', 'type'],
1113
+ code: 'invalid_transport_type',
1114
+ message: 'Transport type must be stdio, http, sse, or streamableHttp.',
1115
+ });
1116
+ }
1117
+ if (url !== undefined && !isValidUrlOrEnvReference(url)) {
1118
+ errors.push({
1119
+ fieldPath: ['transport', 'url'],
1120
+ code: 'invalid_url',
1121
+ message: 'URL must be a valid URL or environment substitution reference.',
1122
+ });
1123
+ }
1124
+ if (serverConfig.args !== undefined &&
1125
+ (!Array.isArray(serverConfig.args) || !serverConfig.args.every((arg) => typeof arg === 'string'))) {
1126
+ errors.push({
1127
+ fieldPath: ['transport', 'args'],
1128
+ code: 'invalid_string_list',
1129
+ message: 'Args must be a list of strings.',
1130
+ });
1131
+ }
1132
+ if (!url && !command) {
1133
+ errors.push({
1134
+ fieldPath: ['transport'],
1135
+ code: 'missing_transport',
1136
+ message: 'Configured server must define a command or URL.',
1137
+ });
1138
+ }
1139
+ return {
1140
+ status: errors.length === 0 ? 'valid' : 'invalid',
1141
+ errors,
1142
+ };
1143
+ }
1144
+ function mergeValidation(editValidation, serverValidation) {
1145
+ const errors = [...editValidation.errors, ...serverValidation.errors];
1146
+ return {
1147
+ status: errors.length === 0 ? 'valid' : 'invalid',
1148
+ errors,
1149
+ };
1150
+ }
1151
+ function isValidUrlOrEnvReference(value) {
1152
+ if (/\$\{[^}]+\}|\$[A-Za-z_][A-Za-z0-9_]*/u.test(value)) {
1153
+ return true;
1154
+ }
1155
+ try {
1156
+ new URL(value);
1157
+ return true;
1158
+ }
1159
+ catch {
1160
+ return false;
1161
+ }
1162
+ }
1163
+ function previewConfigChange(targetName, changed) {
1164
+ return {
1165
+ status: changed ? 'changed' : 'unchanged',
1166
+ operation: 'set_static',
1167
+ configPath: '[redacted]',
1168
+ target: { name: targetName, source: 'mcpServers' },
1169
+ changed,
1170
+ backup: { created: false },
1171
+ retentionCleanup: { attempted: false, deletedPaths: [], warnings: [] },
1172
+ reload: { status: 'skipped' },
1173
+ warnings: [],
1174
+ };
1175
+ }
1176
+ function previewFingerprint(input) {
1177
+ return `preview_${createHash('sha256')
1178
+ .update(stableStringify({
1179
+ schemaVersion: 2,
1180
+ targetName: input.targetName,
1181
+ sourceFingerprint: input.sourceFingerprint,
1182
+ globalConfigFingerprint: input.globalConfigFingerprint,
1183
+ edit: redactEditDraftForFingerprint(input.edit),
1184
+ current: input.current,
1185
+ proposed: input.proposed,
1186
+ diff: input.diff,
1187
+ validation: input.validation,
1188
+ }))
1189
+ .digest('hex')}`;
1190
+ }
1191
+ function redactEditDraftForFingerprint(edit) {
1192
+ return {
1193
+ ...edit,
1194
+ secrets: edit.secrets?.map((secret) => ({
1195
+ fieldPath: secret.fieldPath,
1196
+ action: secret.action,
1197
+ replacement: secret.replacement ? fingerprintSecretReplacement(secret.replacement) : undefined,
1198
+ })),
1199
+ };
1200
+ }
1201
+ function fingerprintSecretReplacement(replacement) {
1202
+ if (replacement.kind === 'environmentReference') {
1203
+ return {
1204
+ kind: replacement.kind,
1205
+ value: normalizeEnvironmentReference(replacement.value),
1206
+ };
1207
+ }
1208
+ return {
1209
+ kind: replacement.kind,
1210
+ value: `hmac-sha256:${fingerprintConfiguredServerSecretValue(replacement.value)}`,
1211
+ };
1212
+ }
1213
+ function createPreviewDiff(current, proposed, edit) {
1214
+ const diff = [];
1215
+ pushDiff(diff, ['id'], current.id, proposed.id, proposed.id === current.id ? [] : ['rename']);
1216
+ pushDiff(diff, ['enabled'], current.enabled, proposed.enabled, ['connection_critical']);
1217
+ pushDiff(diff, ['tags'], current.tags, proposed.tags, []);
1218
+ const secretEditedTopLevelKeys = new Set((edit.secrets ?? []).map((secret) => secret.fieldPath[0]));
1219
+ const transportEditedKeys = new Set(Object.keys(edit.transport ?? {}));
1220
+ const transportKeys = new Set([...Object.keys(current.transport), ...Object.keys(proposed.transport)]);
1221
+ for (const key of transportKeys) {
1222
+ if (secretEditedTopLevelKeys.has(key) && !transportEditedKeys.has(key)) {
1223
+ continue;
1224
+ }
1225
+ const currentValue = current.transport[key];
1226
+ const proposedValue = proposed.transport[key];
1227
+ pushDiff(diff, ['transport', key], currentValue, proposedValue, previewRiskFlags([key]));
1228
+ }
1229
+ for (const secret of edit.secrets ?? []) {
1230
+ if (secret.action === 'preserve' || !isValidFieldPath(secret.fieldPath)) {
1231
+ continue;
1232
+ }
1233
+ diff.push({
1234
+ fieldPath: secret.fieldPath,
1235
+ secretAction: secret.action,
1236
+ oldValue: readRedactedField(current.transport, secret.fieldPath),
1237
+ newValue: redactedSecretPreviewValue(secret),
1238
+ riskFlags: [...previewRiskFlags(secret.fieldPath), 'secret'],
1239
+ });
1240
+ }
1241
+ return diff;
1242
+ }
1243
+ function appendClearedOverrideDiff(diff, current, proposed, edit) {
1244
+ for (const key of edit.clearTransportOverrides ?? []) {
1245
+ if (diff.some((entry) => entry.fieldPath[0] === 'transport' && entry.fieldPath[1] === key)) {
1246
+ continue;
1247
+ }
1248
+ diff.push({
1249
+ fieldPath: ['transport', key],
1250
+ oldValue: current.transport[key],
1251
+ newValue: proposed.transport[key],
1252
+ riskFlags: previewRiskFlags([key]),
1253
+ });
1254
+ }
1255
+ }
1256
+ function pushDiff(diff, fieldPath, oldValue, newValue, riskFlags) {
1257
+ if (stableStringify(oldValue) === stableStringify(newValue)) {
1258
+ return;
1259
+ }
1260
+ diff.push({
1261
+ fieldPath,
1262
+ oldValue,
1263
+ newValue,
1264
+ riskFlags,
1265
+ });
1266
+ }
1267
+ function readRedactedField(source, fieldPath) {
1268
+ if (fieldPath[0] === 'env' && Array.isArray(source.env)) {
1269
+ return redactedValue();
1270
+ }
1271
+ if (fieldPath[0] === 'url') {
1272
+ return readRedactedUrlField(source.url, fieldPath);
1273
+ }
1274
+ if (fieldPath[0] === 'args') {
1275
+ return readRedactedArgsField(source.args, fieldPath);
1276
+ }
1277
+ let cursor = source;
1278
+ for (const segment of fieldPath) {
1279
+ if (!cursor || typeof cursor !== 'object' || Array.isArray(cursor)) {
1280
+ return undefined;
1281
+ }
1282
+ cursor = cursor[segment];
1283
+ }
1284
+ return cursor;
1285
+ }
1286
+ function readRedactedUrlField(value, fieldPath) {
1287
+ if (typeof value !== 'string') {
1288
+ return undefined;
1289
+ }
1290
+ try {
1291
+ const url = new URL(value);
1292
+ const [, scope, key] = fieldPath;
1293
+ if (scope === 'username') {
1294
+ return url.username ? redactedValue() : undefined;
1295
+ }
1296
+ if (scope === 'password') {
1297
+ return url.password ? redactedValue() : undefined;
1298
+ }
1299
+ if (scope === 'query' && key) {
1300
+ return url.searchParams.has(key) ? redactedValue() : undefined;
1301
+ }
1302
+ }
1303
+ catch {
1304
+ return undefined;
1305
+ }
1306
+ return undefined;
1307
+ }
1308
+ function readRedactedArgsField(value, fieldPath) {
1309
+ if (!Array.isArray(value) || fieldPath.length !== 2) {
1310
+ return undefined;
1311
+ }
1312
+ const index = Number(fieldPath[1]);
1313
+ return Number.isInteger(index) && index >= 0 && index < value.length ? redactedValue() : undefined;
1314
+ }
1315
+ function redactedSecretPreviewValue(secret) {
1316
+ if (secret.action === 'clear') {
1317
+ return { present: false, value: '[REDACTED]', secret: true };
1318
+ }
1319
+ if (secret.replacement?.kind === 'environmentReference') {
1320
+ return {
1321
+ kind: 'environmentReference',
1322
+ value: normalizeEnvironmentReference(secret.replacement.value),
1323
+ storesSecretMaterial: false,
1324
+ };
1325
+ }
1326
+ return { present: true, value: '[REDACTED]', secret: true };
1327
+ }
1328
+ function isConnectionCriticalPath(fieldPath) {
1329
+ const joined = fieldPath.join('.');
1330
+ return /^(type|command|url|args|cwd|env|headers|oauth|metadata|template)/u.test(joined);
1331
+ }
1332
+ function previewRiskFlags(fieldPath) {
1333
+ const flags = [];
1334
+ if (isConnectionCriticalPath(fieldPath)) {
1335
+ flags.push('connection_critical');
1336
+ }
1337
+ if (fieldPath.includes('template')) {
1338
+ flags.push('template_risk');
1339
+ }
1340
+ return flags;
1341
+ }
1342
+ function stableStringify(value) {
1343
+ if (Array.isArray(value)) {
1344
+ return `[${value.map((item) => stableStringify(item)).join(',')}]`;
1345
+ }
1346
+ if (value && typeof value === 'object') {
1347
+ const entries = Object.entries(value)
1348
+ .filter(([, entryValue]) => entryValue !== undefined)
1349
+ .sort(([left], [right]) => left.localeCompare(right));
1350
+ return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(',')}}`;
1351
+ }
1352
+ return JSON.stringify(value);
1353
+ }
1354
+ function createConfiguredServerReadModel(name, serverConfig) {
1355
+ const secretInputs = [];
1356
+ const transport = {};
1357
+ for (const [key, value] of Object.entries(serverConfig)) {
1358
+ if (key === 'disabled' || key === 'tags') {
1359
+ continue;
1360
+ }
1361
+ if (key === 'env') {
1362
+ transport.env = redactNamedSecretRecord(value, ['env'], secretInputs);
1363
+ continue;
1364
+ }
1365
+ if (key === 'headers') {
1366
+ transport.headers = redactNamedSecretRecord(value, ['headers'], secretInputs);
1367
+ continue;
1368
+ }
1369
+ if (key === 'args') {
1370
+ transport.args = sanitizeCommandArgs(value, secretInputs);
1371
+ continue;
1372
+ }
1373
+ if (key === 'oauth') {
1374
+ transport.oauth = redactOAuthConfig(value, secretInputs);
1375
+ continue;
1376
+ }
1377
+ if (key === 'url' && typeof value === 'string') {
1378
+ transport.url = redactUrlQuerySecrets(value, secretInputs);
1379
+ continue;
1380
+ }
1381
+ transport[key] = sanitizeUnknownValue(value, [key], secretInputs);
1382
+ }
1383
+ const enabled = !isConfiguredServerTargetDisabled(serverConfig.disabled);
1384
+ return {
1385
+ id: name,
1386
+ source: 'mcpServers',
1387
+ target: {
1388
+ type: 'configured_server',
1389
+ id: name,
1390
+ source: 'mcpServers',
1391
+ },
1392
+ enabled,
1393
+ tags: normalizeTags(serverConfig.tags),
1394
+ transportSummary: createTransportSummary(serverConfig, transport),
1395
+ mutationAvailability: {
1396
+ available: true,
1397
+ operations: ['enable', 'disable'],
1398
+ },
1399
+ actionState: createActionState(name, enabled),
1400
+ transport,
1401
+ secretInputs,
1402
+ };
1403
+ }
1404
+ const STDIO_TRANSPORT_TYPES = ['stdio'];
1405
+ const NETWORK_TRANSPORT_TYPES = ['http', 'sse', 'streamableHttp'];
1406
+ const TRANSPORT_FIELD_DEFINITIONS = [
1407
+ {
1408
+ key: 'type',
1409
+ label: 'Transport Type',
1410
+ control: 'select',
1411
+ options: ['stdio', 'http', 'sse', 'streamableHttp'],
1412
+ },
1413
+ { key: 'timeout', label: 'Deprecated Timeout', control: 'number' },
1414
+ { key: 'connectionTimeout', label: 'Connection Timeout', control: 'number' },
1415
+ { key: 'requestTimeout', label: 'Request Timeout', control: 'number' },
1416
+ { key: 'disabledTools', label: 'Disabled Tools', control: 'string-list', defaultValue: [] },
1417
+ { key: 'template', label: 'Template', control: 'record', defaultValue: {} },
1418
+ {
1419
+ key: 'url',
1420
+ label: 'URL',
1421
+ control: 'text',
1422
+ defaultValue: '',
1423
+ applicableTransportTypes: NETWORK_TRANSPORT_TYPES,
1424
+ },
1425
+ {
1426
+ key: 'headers',
1427
+ label: 'Headers',
1428
+ control: 'record',
1429
+ defaultValue: {},
1430
+ applicableTransportTypes: NETWORK_TRANSPORT_TYPES,
1431
+ },
1432
+ {
1433
+ key: 'oauth',
1434
+ label: 'OAuth',
1435
+ control: 'record',
1436
+ defaultValue: {},
1437
+ applicableTransportTypes: NETWORK_TRANSPORT_TYPES,
1438
+ },
1439
+ {
1440
+ key: 'command',
1441
+ label: 'Command',
1442
+ control: 'text',
1443
+ defaultValue: '',
1444
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1445
+ },
1446
+ {
1447
+ key: 'args',
1448
+ label: 'Args',
1449
+ control: 'string-list',
1450
+ defaultValue: [],
1451
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1452
+ },
1453
+ {
1454
+ key: 'stderr',
1455
+ label: 'Stderr',
1456
+ control: 'text',
1457
+ defaultValue: '',
1458
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1459
+ },
1460
+ {
1461
+ key: 'cwd',
1462
+ label: 'Working Directory',
1463
+ control: 'text',
1464
+ defaultValue: '',
1465
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1466
+ },
1467
+ {
1468
+ key: 'env',
1469
+ label: 'Environment',
1470
+ control: 'record',
1471
+ defaultValue: {},
1472
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1473
+ },
1474
+ {
1475
+ key: 'inheritParentEnv',
1476
+ label: 'Inherit Parent Environment',
1477
+ control: 'switch',
1478
+ defaultValue: false,
1479
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1480
+ },
1481
+ {
1482
+ key: 'envFilter',
1483
+ label: 'Environment Filter',
1484
+ control: 'string-list',
1485
+ defaultValue: [],
1486
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1487
+ },
1488
+ {
1489
+ key: 'restartOnExit',
1490
+ label: 'Restart On Exit',
1491
+ control: 'switch',
1492
+ defaultValue: false,
1493
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1494
+ },
1495
+ {
1496
+ key: 'maxRestarts',
1497
+ label: 'Maximum Restarts',
1498
+ control: 'number',
1499
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1500
+ },
1501
+ {
1502
+ key: 'restartDelay',
1503
+ label: 'Restart Delay',
1504
+ control: 'number',
1505
+ applicableTransportTypes: STDIO_TRANSPORT_TYPES,
1506
+ },
1507
+ ];
1508
+ const TRANSPORT_FIELD_DEFINITION_BY_KEY = new Map(TRANSPORT_FIELD_DEFINITIONS.map((definition) => [definition.key, definition]));
1509
+ function createConfiguredServerEditContract(server, rawServerConfig, serverDefaults) {
1510
+ const secretFieldKeys = new Set(server.secretInputs.map((input) => fieldKey(input.fieldPath)));
1511
+ const transportFields = TRANSPORT_FIELD_DEFINITIONS.map((definition) => {
1512
+ const value = Object.prototype.hasOwnProperty.call(server.transport, definition.key)
1513
+ ? server.transport[definition.key]
1514
+ : definition.defaultValue;
1515
+ return transportEditField(definition.key, omitSecretValues(value, [definition.key], secretFieldKeys), definition, transportFieldOwnership(definition.key, rawServerConfig, serverDefaults));
1516
+ });
1517
+ const knownTransportFields = new Set(TRANSPORT_FIELD_DEFINITIONS.map((definition) => definition.key));
1518
+ for (const [key, value] of Object.entries(server.transport)) {
1519
+ if (knownTransportFields.has(key) || secretFieldKeys.has(key))
1520
+ continue;
1521
+ transportFields.push(transportEditField(key, omitSecretValues(value, [key], secretFieldKeys), undefined, transportFieldOwnership(key, rawServerConfig, serverDefaults)));
1522
+ }
1523
+ return {
1524
+ schemaVersion: 3,
1525
+ target: server.target,
1526
+ capabilities: {
1527
+ singleTargetEdit: true,
1528
+ rename: { supported: true },
1529
+ create: { supported: false },
1530
+ delete: { supported: false },
1531
+ bulkEdit: { supported: false },
1532
+ rawJson: { supported: false },
1533
+ preview: { supported: true },
1534
+ apply: { supported: true },
1535
+ },
1536
+ fieldGroups: [
1537
+ {
1538
+ id: 'identity',
1539
+ label: 'Target',
1540
+ fields: [
1541
+ {
1542
+ fieldPath: ['id'],
1543
+ label: 'Target ID',
1544
+ control: 'text',
1545
+ value: server.id,
1546
+ editable: true,
1547
+ },
1548
+ {
1549
+ fieldPath: ['enabled'],
1550
+ label: 'Enabled',
1551
+ control: 'switch',
1552
+ value: server.enabled,
1553
+ editable: true,
1554
+ },
1555
+ {
1556
+ fieldPath: ['tags'],
1557
+ label: 'Tags',
1558
+ control: 'tag-list',
1559
+ value: server.tags,
1560
+ editable: true,
1561
+ },
1562
+ ],
1563
+ },
1564
+ {
1565
+ id: 'transport',
1566
+ label: 'Transport',
1567
+ fields: transportFields,
1568
+ },
1569
+ {
1570
+ id: 'secrets',
1571
+ label: 'Secrets',
1572
+ fields: server.secretInputs.map((input) => secretEditField(input, secretFieldOwnership(input.fieldPath, rawServerConfig, serverDefaults))),
1573
+ },
1574
+ ],
1575
+ };
1576
+ }
1577
+ function omitSecretValues(value, fieldPath, secretFieldKeys) {
1578
+ if (secretFieldKeys.has(fieldKey(fieldPath))) {
1579
+ return undefined;
1580
+ }
1581
+ if (Array.isArray(value)) {
1582
+ return value
1583
+ .map((entry, index) => omitSecretValues(entry, [...fieldPath, String(index)], secretFieldKeys))
1584
+ .filter((entry) => entry !== undefined);
1585
+ }
1586
+ if (!value || typeof value !== 'object') {
1587
+ return value;
1588
+ }
1589
+ const cleaned = {};
1590
+ for (const [key, nestedValue] of Object.entries(value)) {
1591
+ const nextValue = omitSecretValues(nestedValue, [...fieldPath, key], secretFieldKeys);
1592
+ if (nextValue !== undefined) {
1593
+ cleaned[key] = nextValue;
1594
+ }
1595
+ }
1596
+ return cleaned;
1597
+ }
1598
+ function transportEditField(key, value, definition = TRANSPORT_FIELD_DEFINITION_BY_KEY.get(key), ownership = transportFieldOwnership(key, {}, undefined)) {
1599
+ const overrideSupported = GLOBAL_TRANSPORT_CONFIG_KEYS.includes(key);
1600
+ const overrideMetadata = {
1601
+ source: ownership,
1602
+ overrideSupported,
1603
+ clearOverrideSupported: overrideSupported && ownership === 'server',
1604
+ };
1605
+ if (definition) {
1606
+ return {
1607
+ fieldPath: ['transport', key],
1608
+ label: definition.label,
1609
+ control: definition.control,
1610
+ value,
1611
+ options: definition.options,
1612
+ editable: true,
1613
+ applicableTransportTypes: definition.applicableTransportTypes,
1614
+ ...overrideMetadata,
1615
+ };
1616
+ }
1617
+ if (Array.isArray(value)) {
1618
+ return {
1619
+ fieldPath: ['transport', key],
1620
+ label: labelFromPath([key]),
1621
+ control: 'string-list',
1622
+ value,
1623
+ editable: true,
1624
+ ...overrideMetadata,
1625
+ };
1626
+ }
1627
+ if (value && typeof value === 'object') {
1628
+ return {
1629
+ fieldPath: ['transport', key],
1630
+ label: labelFromPath([key]),
1631
+ control: 'record',
1632
+ value,
1633
+ editable: true,
1634
+ ...overrideMetadata,
1635
+ };
1636
+ }
1637
+ return {
1638
+ fieldPath: ['transport', key],
1639
+ label: labelFromPath([key]),
1640
+ control: typeof value === 'boolean' ? 'switch' : 'text',
1641
+ value,
1642
+ editable: true,
1643
+ ...overrideMetadata,
1644
+ };
1645
+ }
1646
+ function transportFieldOwnership(key, rawServerConfig, serverDefaults) {
1647
+ if (Object.prototype.hasOwnProperty.call(rawServerConfig, key))
1648
+ return 'server';
1649
+ if (serverDefaults && Object.prototype.hasOwnProperty.call(serverDefaults, key))
1650
+ return 'inherited';
1651
+ return 'default';
1652
+ }
1653
+ function secretFieldOwnership(fieldPath, rawServerConfig, serverDefaults) {
1654
+ const [key, nestedKey] = fieldPath;
1655
+ if (key === 'env' && nestedKey) {
1656
+ const rawEnv = rawServerConfig.env;
1657
+ if (rawEnv && !Array.isArray(rawEnv) && Object.prototype.hasOwnProperty.call(rawEnv, nestedKey))
1658
+ return 'server';
1659
+ const defaultEnv = serverDefaults?.env;
1660
+ if (defaultEnv && !Array.isArray(defaultEnv) && Object.prototype.hasOwnProperty.call(defaultEnv, nestedKey)) {
1661
+ return 'inherited';
1662
+ }
1663
+ }
1664
+ return transportFieldOwnership(key, rawServerConfig, serverDefaults);
1665
+ }
1666
+ function secretEditField(input, source = 'server') {
1667
+ const overrideSupported = GLOBAL_TRANSPORT_CONFIG_KEYS.includes(input.fieldPath[0]);
1668
+ const allowedActions = source === 'inherited' ? input.allowedActions.filter((action) => action !== 'clear') : input.allowedActions;
1669
+ return {
1670
+ fieldPath: input.fieldPath,
1671
+ label: input.label,
1672
+ control: 'secret',
1673
+ editable: true,
1674
+ applicableTransportTypes: transportFieldApplicability(input.fieldPath[0]),
1675
+ source,
1676
+ overrideSupported,
1677
+ clearOverrideSupported: overrideSupported && source === 'server',
1678
+ secret: {
1679
+ state: input.state,
1680
+ defaultAction: 'preserve',
1681
+ allowedActions,
1682
+ environmentReference: {
1683
+ supported: true,
1684
+ recommended: true,
1685
+ valueFormat: 'env_var_name_or_substitution',
1686
+ storesSecretMaterial: false,
1687
+ guidance: 'Store only the environment variable name or substitution expression; keep secret material outside 1MCP config.',
1688
+ },
1689
+ inlineReplacement: {
1690
+ supported: true,
1691
+ emphasis: 'secondary',
1692
+ guidance: 'Use inline replacement only as a secondary path when an environment reference is not suitable.',
1693
+ },
1694
+ },
1695
+ };
1696
+ }
1697
+ function fieldKey(fieldPath) {
1698
+ return fieldPath.join('\0');
1699
+ }
1700
+ function labelFromPath(pathSegments) {
1701
+ return pathSegments
1702
+ .join(' ')
1703
+ .replace(/([a-z])([A-Z])/gu, '$1 $2')
1704
+ .replace(/[-_.]+/gu, ' ')
1705
+ .replace(/\b\w/gu, (char) => char.toUpperCase());
1706
+ }
1707
+ function createActionState(name, enabled) {
1708
+ return {
1709
+ enable: enabled
1710
+ ? {
1711
+ available: false,
1712
+ disabledReason: 'already_enabled',
1713
+ label: `Enable ${name}`,
1714
+ }
1715
+ : {
1716
+ available: true,
1717
+ label: `Enable ${name}`,
1718
+ },
1719
+ disable: enabled
1720
+ ? {
1721
+ available: true,
1722
+ label: `Disable ${name}`,
1723
+ }
1724
+ : {
1725
+ available: false,
1726
+ disabledReason: 'already_disabled',
1727
+ label: `Disable ${name}`,
1728
+ },
1729
+ };
1730
+ }
1731
+ function createTransportSummary(serverConfig, transport) {
1732
+ const kind = transportKind(serverConfig);
1733
+ const url = typeof transport.url === 'string' ? transport.url : undefined;
1734
+ const command = typeof serverConfig.command === 'string' ? serverConfig.command : undefined;
1735
+ const args = Array.isArray(transport.args)
1736
+ ? transport.args.filter((arg) => typeof arg === 'string')
1737
+ : [];
1738
+ if (url) {
1739
+ return { kind, label: url };
1740
+ }
1741
+ if (command) {
1742
+ return {
1743
+ kind,
1744
+ label: [command, ...args].join(' '),
1745
+ };
1746
+ }
1747
+ return { kind, label: kind };
1748
+ }
1749
+ function sanitizeCommandArgs(value, secretInputs) {
1750
+ if (!Array.isArray(value)) {
1751
+ return sanitizeUnknownValue(value, ['args'], secretInputs);
1752
+ }
1753
+ let pendingSecretInput;
1754
+ return value.map((entry, index) => {
1755
+ const fieldPath = ['args', String(index)];
1756
+ if (typeof entry !== 'string') {
1757
+ pendingSecretInput = undefined;
1758
+ return sanitizeUnknownValue(entry, fieldPath, secretInputs);
1759
+ }
1760
+ if (pendingSecretInput) {
1761
+ secretInputs.push(secretInput(fieldPath, pendingSecretInput.label));
1762
+ pendingSecretInput = undefined;
1763
+ return 'REDACTED';
1764
+ }
1765
+ const assignment = secretAssignmentArg(entry);
1766
+ if (assignment) {
1767
+ secretInputs.push(secretInput(fieldPath, `args.${assignment.label}`));
1768
+ return `${assignment.prefix}REDACTED`;
1769
+ }
1770
+ if (containsSecretLikeString(entry)) {
1771
+ secretInputs.push(secretInput(fieldPath, `args.${fieldPath[1]}`));
1772
+ return redactSecretArg(entry);
1773
+ }
1774
+ const secretFlag = secretFlagArg(entry);
1775
+ if (secretFlag) {
1776
+ pendingSecretInput = { fieldPath, label: `args.${secretFlag}` };
1777
+ return entry;
1778
+ }
1779
+ if (secretCarrierArg(entry)) {
1780
+ pendingSecretInput = { fieldPath, label: `args.${entry}` };
1781
+ return entry;
1782
+ }
1783
+ return entry;
1784
+ });
1785
+ }
1786
+ function secretAssignmentArg(value) {
1787
+ const match = value.match(/^((-{1,2}[^=\s]+)|([A-Za-z_][A-Za-z0-9_]*))=(.*)$/u);
1788
+ if (!match) {
1789
+ return null;
1790
+ }
1791
+ const key = match[1];
1792
+ return isSecretLikeKey(key) ? { prefix: `${key}=`, label: key } : null;
1793
+ }
1794
+ function redactSecretArg(value) {
1795
+ if (value.includes('=')) {
1796
+ return `${value.split('=', 1)[0]}=REDACTED`;
1797
+ }
1798
+ return 'REDACTED';
1799
+ }
1800
+ function secretFlagArg(value) {
1801
+ return /^-{1,2}[^=\s]+$/u.test(value) && isSecretLikeKey(value) ? value : undefined;
1802
+ }
1803
+ function transportKind(serverConfig) {
1804
+ if (typeof serverConfig.type === 'string') {
1805
+ return serverConfig.type;
1806
+ }
1807
+ if (typeof serverConfig.url === 'string') {
1808
+ return 'http';
1809
+ }
1810
+ if (typeof serverConfig.command === 'string') {
1811
+ return 'stdio';
1812
+ }
1813
+ return 'unknown';
1814
+ }
1815
+ function wouldUseLocalStdioTransport(serverConfig) {
1816
+ return serverConfig.type === 'stdio' || (!serverConfig.type && typeof serverConfig.command === 'string');
1817
+ }
1818
+ function normalizeTags(tags) {
1819
+ return Array.isArray(tags) ? tags.filter((tag) => typeof tag === 'string') : [];
1820
+ }
1821
+ function redactNamedSecretRecord(value, basePath, secretInputs) {
1822
+ if (Array.isArray(value)) {
1823
+ return value.map((entry, index) => {
1824
+ const label = typeof entry === 'string' && entry.includes('=') ? entry.split('=')[0] : String(index);
1825
+ secretInputs.push(secretInput([...basePath, label], label));
1826
+ return redactedValue();
1827
+ });
1828
+ }
1829
+ if (!value || typeof value !== 'object') {
1830
+ return {};
1831
+ }
1832
+ const redacted = {};
1833
+ for (const key of Object.keys(value)) {
1834
+ redacted[key] = redactedValue();
1835
+ secretInputs.push(secretInput([...basePath, key], key));
1836
+ }
1837
+ return redacted;
1838
+ }
1839
+ function redactOAuthConfig(value, secretInputs) {
1840
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1841
+ return {};
1842
+ }
1843
+ const redacted = {};
1844
+ for (const [key, fieldValue] of Object.entries(value)) {
1845
+ if (typeof fieldValue === 'string') {
1846
+ redacted[key] = redactedValue();
1847
+ secretInputs.push(secretInput(['oauth', key], key));
1848
+ }
1849
+ else {
1850
+ redacted[key] = sanitizeUnknownValue(fieldValue, ['oauth', key], secretInputs);
1851
+ }
1852
+ }
1853
+ return redacted;
1854
+ }
1855
+ function redactUrlQuerySecrets(value, secretInputs) {
1856
+ try {
1857
+ const url = new URL(value);
1858
+ if (url.username) {
1859
+ url.username = 'REDACTED';
1860
+ secretInputs.push(secretInput(['url', 'username'], 'url.username'));
1861
+ }
1862
+ if (url.password) {
1863
+ url.password = 'REDACTED';
1864
+ secretInputs.push(secretInput(['url', 'password'], 'url.password'));
1865
+ }
1866
+ for (const key of Array.from(url.searchParams.keys())) {
1867
+ const value = url.searchParams.get(key) ?? '';
1868
+ if (isSecretLikeKey(key) || containsSecretLikeString(value)) {
1869
+ url.searchParams.set(key, 'REDACTED');
1870
+ secretInputs.push(secretInput(['url', 'query', key], `url.query.${key}`));
1871
+ }
1872
+ }
1873
+ return url.toString();
1874
+ }
1875
+ catch {
1876
+ let redacted = value.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^@/?#]+@/iu, (_match, prefix) => {
1877
+ secretInputs.push(secretInput(['url', 'userinfo'], 'url.userinfo', ['preserve']));
1878
+ return `${prefix}REDACTED@`;
1879
+ });
1880
+ redacted = redacted.replace(/([?&]([^=]*(?:token|secret|password|auth|key)[^=]*)=)[^&]*/giu, (_match, prefix, key) => {
1881
+ secretInputs.push(secretInput(['url', 'query', key], `url.query.${key}`));
1882
+ return `${prefix}REDACTED`;
1883
+ });
1884
+ return redacted;
1885
+ }
1886
+ }
1887
+ function sanitizeUnknownValue(value, fieldPath, secretInputs) {
1888
+ if (isSecretLikeKey(fieldPath[fieldPath.length - 1])) {
1889
+ secretInputs.push(secretInput(fieldPath, fieldPath.join('.')));
1890
+ return redactedValue();
1891
+ }
1892
+ if (typeof value === 'string' && containsSecretLikeString(value)) {
1893
+ secretInputs.push(secretInput(fieldPath, fieldPath.join('.')));
1894
+ return redactedValue();
1895
+ }
1896
+ if (Array.isArray(value)) {
1897
+ return value.map((entry, index) => sanitizeUnknownValue(entry, [...fieldPath, String(index)], secretInputs));
1898
+ }
1899
+ if (!value || typeof value !== 'object') {
1900
+ return value;
1901
+ }
1902
+ const sanitized = {};
1903
+ for (const [key, nestedValue] of Object.entries(value)) {
1904
+ sanitized[key] = sanitizeUnknownValue(nestedValue, [...fieldPath, key], secretInputs);
1905
+ }
1906
+ return sanitized;
1907
+ }
1908
+ function isSecretLikeKey(key) {
1909
+ return /api[_-]?key|access[_-]?token|token|password|secret|auth|credential|private[_-]?key/i.test(key);
1910
+ }
1911
+ function secretInput(fieldPath, label = fieldPath[fieldPath.length - 1], allowedActions = ['preserve', 'replace', 'clear']) {
1912
+ return {
1913
+ fieldPath,
1914
+ label,
1915
+ state: 'present',
1916
+ allowedActions,
1917
+ };
1918
+ }
1919
+ function redactedValue() {
1920
+ return {
1921
+ present: true,
1922
+ value: '[REDACTED]',
1923
+ secret: true,
1924
+ };
1925
+ }
1926
+ //# sourceMappingURL=adminConfiguredServerService.js.map