Do not implement until the current behavior is confirmed. Read paste_json / dropEvent in bcc.py and apply_servers in bcc_core.py, then post a comment on this issue describing what actually happens today when a pasted/dropped server name collides with an existing one. If it silently overwrites, then code the fix. If it already prompts, close this issue.
Problem (if behavior is silent overwrite)
parse_pasted_json_verbose / apply_servers may silently overwrite an existing server when a pasted or drag-and-dropped server name matches one already present. Silent overwrite matches the "config got clobbered" complaint pattern.
Proposed fix (if needed)
Add an explicit prompt (overwrite / rename / skip) when a pasted or dropped server name collides with an existing entry.
Acceptance criteria (if needed)
Pasting a server with a name that already exists prompts the user instead of silently overwriting.
Renaming and skipping both work correctly.
Relevant code
bcc_core.py::apply_servers (line 186)
bcc_core.py::parse_pasted_json_verbose (line 531)
bcc.py paste/drop handlers
## Status: verify before coding
**Do not implement until the current behavior is confirmed.** Read `paste_json` / `dropEvent` in `bcc.py` and `apply_servers` in `bcc_core.py`, then post a comment on this issue describing what actually happens today when a pasted/dropped server name collides with an existing one. If it silently overwrites, then code the fix. If it already prompts, close this issue.
## Problem (if behavior is silent overwrite)
`parse_pasted_json_verbose` / `apply_servers` may silently overwrite an existing server when a pasted or drag-and-dropped server name matches one already present. Silent overwrite matches the "config got clobbered" complaint pattern.
## Proposed fix (if needed)
Add an explicit prompt (overwrite / rename / skip) when a pasted or dropped server name collides with an existing entry.
## Acceptance criteria (if needed)
- Pasting a server with a name that already exists prompts the user instead of silently overwriting.
- Renaming and skipping both work correctly.
## Relevant code
- `bcc_core.py::apply_servers` (line 186)
- `bcc_core.py::parse_pasted_json_verbose` (line 531)
- `bcc.py` paste/drop handlers
the_og
added the P1 label 2026-07-02 00:50:45 -04:00
Investigated current behavior before touching anything, per the issue instructions.
Paste path (PasteDialog → MainWindow.paste_json, bcc.py ~1623-1653, using core.parse_pasted_json_verbose in bcc_core.py ~700): on a name collision it already shows a QMessageBox.question — "'{name}' already exists. Replace it? Yes = replace · No = keep both (renamed)". Choosing "keep both" appends -2, -3, ... until a free name is found. This path was already safe.
Drag-and-drop path (MainWindow.dropEvent, bcc.py ~1826-1848): this did not go through the same prompt. It silently renamed any colliding server to a fixed "{name}-imported" suffix, with no check that the suffixed name was itself free:
No prompt at all on drop — inconsistent with paste, and the user gets no say in replace-vs-rename.
If "{name}-imported" was also already taken (e.g. dropping the same file twice, or a server that already ends in -imported), the code did nothing further — two ServerEntry objects with the identical .name end up in self.servers. That isn't visible immediately, but apply_servers() (bcc_core.py ~192-198) builds the on-disk mcpServers dict via {s.name: s.data for s in servers if s.enabled} — a plain dict comprehension, last-one-wins, no warning. So on Save, one of the two same-named entries is silently dropped, and its config is lost.
So: paste already handled collisions safely; drag-and-drop did not, and had a latent silent-data-loss bug on repeated/edge-case collisions. This is a real gap, not just theoretical, so I'm implementing a fix rather than closing the issue.
Fix: extracted the collision-resolution used by paste (-2, -3, ... loop) into a pure, tested function core.resolve_name_collision(name, existing) -> str in bcc_core.py, and factored both paste_json and dropEvent to go through one shared MainWindow._import_server(name, data) helper that always prompts (replace / keep-both-renamed) on collision and always renames via the guaranteed-unique resolver. Drag-and-drop now behaves identically to paste — no more silent overwrite/collapse. Added unit tests for resolve_name_collision in tests/test_core.py (no-conflict passthrough, single collision, multiple taken suffixes, empty existing set, and a repeated-call scenario simulating a double-drop that previously could collide).
PR incoming.
Investigated current behavior before touching anything, per the issue instructions.
**Paste path** (`PasteDialog` → `MainWindow.paste_json`, bcc.py ~1623-1653, using `core.parse_pasted_json_verbose` in bcc_core.py ~700): on a name collision it already shows a `QMessageBox.question` — "'{name}' already exists. Replace it? Yes = replace · No = keep both (renamed)". Choosing "keep both" appends `-2`, `-3`, ... until a free name is found. This path was already safe.
**Drag-and-drop path** (`MainWindow.dropEvent`, bcc.py ~1826-1848): this did **not** go through the same prompt. It silently renamed any colliding server to a fixed `"{name}-imported"` suffix, with **no check that the suffixed name was itself free**:
```python
for name, data in servers.items():
names = {s.name for s in self.servers}
if name in names:
name = f"{name}-imported"
self.servers.append(core.ServerEntry(name, data, True))
```
Two concrete problems:
1. No prompt at all on drop — inconsistent with paste, and the user gets no say in replace-vs-rename.
2. If `"{name}-imported"` was *also* already taken (e.g. dropping the same file twice, or a server that already ends in `-imported`), the code did nothing further — two `ServerEntry` objects with the identical `.name` end up in `self.servers`. That isn't visible immediately, but `apply_servers()` (bcc_core.py ~192-198) builds the on-disk `mcpServers` dict via `{s.name: s.data for s in servers if s.enabled}` — a plain dict comprehension, last-one-wins, no warning. So on Save, one of the two same-named entries is **silently dropped**, and its config is lost.
So: paste already handled collisions safely; drag-and-drop did not, and had a latent silent-data-loss bug on repeated/edge-case collisions. This is a real gap, not just theoretical, so I'm implementing a fix rather than closing the issue.
**Fix**: extracted the collision-resolution used by paste (`-2`, `-3`, ... loop) into a pure, tested function `core.resolve_name_collision(name, existing) -> str` in bcc_core.py, and factored both `paste_json` and `dropEvent` to go through one shared `MainWindow._import_server(name, data)` helper that always prompts (replace / keep-both-renamed) on collision and always renames via the guaranteed-unique resolver. Drag-and-drop now behaves identically to paste — no more silent overwrite/collapse. Added unit tests for `resolve_name_collision` in tests/test_core.py (no-conflict passthrough, single collision, multiple taken suffixes, empty existing set, and a repeated-call scenario simulating a double-drop that previously could collide).
PR incoming.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Status: verify before coding
Do not implement until the current behavior is confirmed. Read
paste_json/dropEventinbcc.pyandapply_serversinbcc_core.py, then post a comment on this issue describing what actually happens today when a pasted/dropped server name collides with an existing one. If it silently overwrites, then code the fix. If it already prompts, close this issue.Problem (if behavior is silent overwrite)
parse_pasted_json_verbose/apply_serversmay silently overwrite an existing server when a pasted or drag-and-dropped server name matches one already present. Silent overwrite matches the "config got clobbered" complaint pattern.Proposed fix (if needed)
Add an explicit prompt (overwrite / rename / skip) when a pasted or dropped server name collides with an existing entry.
Acceptance criteria (if needed)
Relevant code
bcc_core.py::apply_servers(line 186)bcc_core.py::parse_pasted_json_verbose(line 531)bcc.pypaste/drop handlersInvestigated current behavior before touching anything, per the issue instructions.
Paste path (
PasteDialog→MainWindow.paste_json, bcc.py ~1623-1653, usingcore.parse_pasted_json_verbosein bcc_core.py ~700): on a name collision it already shows aQMessageBox.question— "'{name}' already exists. Replace it? Yes = replace · No = keep both (renamed)". Choosing "keep both" appends-2,-3, ... until a free name is found. This path was already safe.Drag-and-drop path (
MainWindow.dropEvent, bcc.py ~1826-1848): this did not go through the same prompt. It silently renamed any colliding server to a fixed"{name}-imported"suffix, with no check that the suffixed name was itself free:Two concrete problems:
"{name}-imported"was also already taken (e.g. dropping the same file twice, or a server that already ends in-imported), the code did nothing further — twoServerEntryobjects with the identical.nameend up inself.servers. That isn't visible immediately, butapply_servers()(bcc_core.py ~192-198) builds the on-diskmcpServersdict via{s.name: s.data for s in servers if s.enabled}— a plain dict comprehension, last-one-wins, no warning. So on Save, one of the two same-named entries is silently dropped, and its config is lost.So: paste already handled collisions safely; drag-and-drop did not, and had a latent silent-data-loss bug on repeated/edge-case collisions. This is a real gap, not just theoretical, so I'm implementing a fix rather than closing the issue.
Fix: extracted the collision-resolution used by paste (
-2,-3, ... loop) into a pure, tested functioncore.resolve_name_collision(name, existing) -> strin bcc_core.py, and factored bothpaste_jsonanddropEventto go through one sharedMainWindow._import_server(name, data)helper that always prompts (replace / keep-both-renamed) on collision and always renames via the guaranteed-unique resolver. Drag-and-drop now behaves identically to paste — no more silent overwrite/collapse. Added unit tests forresolve_name_collisionin tests/test_core.py (no-conflict passthrough, single collision, multiple taken suffixes, empty existing set, and a repeated-call scenario simulating a double-drop that previously could collide).PR incoming.