MCP password inputs are lost on other machines with Settings Sync: encrypted `mcpInputs` is synced but its key is machine-local
## Summary With Settings Sync enabled on several machines, MCP `promptString` inputs with `"password": true` in the user `mcp.json` keep getting lost. Entering the secrets on machine A makes VS Code prompt for them again on machine B. Entering them on B makes A prompt again, and so on. ## Environment - VS Code 1.144.0 (2c33b50b16c472efe58357d1d2a8a5414bc23fbf), x64 - Windows 11 (10.0.26200) - Settings Sync enabled on 3 machines, with "UI State" included - Each machine uses a different token for the same input IDs (but see below: identical tokens would not help) ## Steps to reproduce 1. On two machines signed into the same Settings Sync account, use a user-level `mcp.json` with a password input: ```jsonc { "inputs": [ { "type": "promptString", "id": "my-token", "description": "Token", "password": true } ], "servers": { "example": { "type": "stdio", "command": "some-mcp-server", "env": { "TOKEN": "${input:my-token}" } } } } ``` 2. On machine A, start the server and enter the token. 3. Let Settings Sync run, then start the server on machine B. B prompts for the token. Enter it. 4. Let Settings Sync run, then restart the server on machine A. **Expected:** Each machine keeps its own saved secret. **Actual:** Machine A prompts for the token again. The machines keep invalidating each other. ## Cause (from reading the source) In `src/vs/workbench/contrib/mcp/common/mcpRegistryInputStorage.ts`: - Secret input values are AES-GCM encrypted into `mcpInputs.secrets` (`{ iv, value }`). - The encryption key (`mcpEncryptionKey`) is generated per machine and stored in `ISecretStorageService`, which is not synced. In `src/vs/workbench/contrib/mcp/common/mcpRegistry.ts`: ```ts private readonly _profileStorage = new Lazy(() => ... createInstance(McpRegistryInputStorage, StorageScope.PROFILE, StorageTarget.USER)); ``` Because of `StorageTarget.USER`, the `mcpInputs` entry, including the encrypted `secrets` blob, is synced as global state. On the receiving machine, `_unsealSecrets()` fails to decrypt with that machine's key, logs `Error unsealing MCP secrets`, sets `secrets = undefined`, and returns `{}`. The user is prompted again, and the newly sealed blob syncs back and breaks the other machine. Local sync history confirms this: every snapshot in `%APPDATA%\Code\User\sync\globalState\` contains `mcpInputs` with `secrets.iv`/`secrets.value`. The blob size changes between snapshots taken minutes apart (e.g. 3340 → 448 → 912 → 1300 bytes) as different machines overwrite it. ## Possible fixes - Store the encrypted secrets under a `StorageTarget.MACHINE` key, separate from the plain-text `values`, so only non-secret inputs sync. Or: - Leave secrets out of the synced payload, or merge per machine, so one machine's undecryptable blob doesn't overwrite another's. ## Workarounds - Exclude "UI State" from Settings Sync. This also stops other UI state from syncing. - Don't use `password` inputs. Use `envFile` or `${env:...}` with machine-local values instead.