MCPcopy
hub / github.com/codeaashu/claude-code / saveMcpServerUserConfig

Function saveMcpServerUserConfig

src/utils/plugins/mcpbHandler.ts:193–341  ·  view source on GitHub ↗
(
  pluginId: string,
  serverName: string,
  config: UserConfigValues,
  schema: UserConfigSchema,
)

Source from the content-addressed store, hash-verified

191 * or channels[].userConfig) — drives the sensitive/non-sensitive split
192 */
193export function saveMcpServerUserConfig(
194 pluginId: string,
195 serverName: string,
196 config: UserConfigValues,
197 schema: UserConfigSchema,
198): void {
199 try {
200 const nonSensitive: UserConfigValues = {}
201 const sensitive: Record<string, string> = {}
202
203 for (const [key, value] of Object.entries(config)) {
204 if (schema[key]?.sensitive === true) {
205 sensitive[key] = String(value)
206 } else {
207 nonSensitive[key] = value
208 }
209 }
210
211 // Scrub ONLY keys we're writing in this call. Covers both directions
212 // across schema-version flips:
213 // - sensitive→secureStorage ⇒ remove stale plaintext from settings.json
214 // - nonSensitive→settings.json ⇒ remove stale entry from secureStorage
215 // (otherwise loadMcpServerUserConfig's {...nonSensitive, ...sensitive}
216 // would let the stale secureStorage value win on next read)
217 // Partial `config` (user only re-enters one field) leaves other fields
218 // untouched in BOTH stores — defense-in-depth against future callers.
219 const sensitiveKeysInThisSave = new Set(Object.keys(sensitive))
220 const nonSensitiveKeysInThisSave = new Set(Object.keys(nonSensitive))
221
222 // Sensitive → secureStorage FIRST. If this fails (keychain locked,
223 // .credentials.json perms), throw before touching settings.json — the
224 // old plaintext stays as a fallback instead of losing BOTH copies.
225 //
226 // Also scrub non-sensitive keys from secureStorage — schema flipped
227 // sensitive→false and they're being written to settings.json now. Without
228 // this, loadMcpServerUserConfig's merge would let the stale secureStorage
229 // value win on next read.
230 const storage = getSecureStorage()
231 const k = serverSecretsKey(pluginId, serverName)
232 const existingInSecureStorage =
233 storage.read()?.pluginSecrets?.[k] ?? undefined
234 const secureScrubbed = existingInSecureStorage
235 ? Object.fromEntries(
236 Object.entries(existingInSecureStorage).filter(
237 ([key]) => !nonSensitiveKeysInThisSave.has(key),
238 ),
239 )
240 : undefined
241 const needSecureScrub =
242 secureScrubbed &&
243 existingInSecureStorage &&
244 Object.keys(secureScrubbed).length !==
245 Object.keys(existingInSecureStorage).length
246 if (Object.keys(sensitive).length > 0 || needSecureScrub) {
247 const existing = storage.read() ?? {}
248 if (!existing.pluginSecrets) {
249 existing.pluginSecrets = {}
250 }

Callers 2

loadMcpbFileFunction · 0.85
PluginOptionsFlowFunction · 0.85

Calls 11

getSecureStorageFunction · 0.85
serverSecretsKeyFunction · 0.85
logForDebuggingFunction · 0.85
updateSettingsForSourceFunction · 0.85
toErrorFunction · 0.85
entriesMethod · 0.80
keysMethod · 0.80
readMethod · 0.65
updateMethod · 0.65
logErrorFunction · 0.50
hasMethod · 0.45

Tested by

no test coverage detected