fix(extensions): retain servers for active clients
This commit is contained in:
1 parent
eeb437e234
commit
5c1b8fedba
2 files changed
+15
-4
No files matched your search
@@ -14,7 +14,7 @@
|
||||
## Menus and reconciliation
|
||||
|
||||
- [x] Build update-menu checks per client when `Both` is selected. Both menus inspect Claude's installed plugins for every entry, so a Codex resource can start unchecked and the combined sync can remove it. (`scripts/lib/plugins.ps1:98`, `scripts/lib/plugins.sh:108`)
|
||||
- [ ] Keep shared MCPorter servers until neither client needs them. Reconciliation processes `shared` records even when only one client is selected; uninstalling that client with an empty selection removes servers still used by the other. (`scripts/lib/managed-extensions.py:463`, `scripts/commands/uninstall.ps1:59`, `scripts/commands/uninstall.sh:77`)
|
||||
- [x] Keep shared MCPorter servers until neither client needs them. Reconciliation processes `shared` records even when only one client is selected; uninstalling that client with an empty selection removes servers still used by the other. (`scripts/lib/managed-extensions.py:463`, `scripts/commands/uninstall.ps1:59`, `scripts/commands/uninstall.sh:77`)
|
||||
- [ ] Manage global packages once across clients. With `--client both`, MCPorter is installed and recorded twice; QMD's global package can be removed during a Codex-only deselection while Claude still uses its plugin. (`scripts/lib/managed-extensions.py:342`, `scripts/lib/managed-extensions.py:378`, `scripts/lib/managed-extensions.py:463`)
|
||||
- [x] Fix Bash `auto` semantic retrieval selection. Both `test_semantic_retrieval_device()` and its caller read `$?` after a completed `if`, losing the benchmark's exit code; an unsuitable device produces an empty choice instead of `false`. (`scripts/lib/semantic-retrieval.sh:13`, `scripts/lib/install-options.sh:52`)
|
||||
- [x] Make ordinary update dry-runs use the saved/current selection. Both menus bypass selection in dry-run and select every manifest entry, so the preview can diverge from the real update. (`scripts/lib/plugins.ps1:91`, `scripts/lib/plugins.sh:97`)
|
||||
|
||||
@@ -202,6 +202,8 @@ class Manager:
|
||||
raise ValueError('Invalid owned CLI record.')
|
||||
elif resource['kind'] == 'mcp' and (resource['client'] != 'shared' or not re.fullmatch(r'[a-z0-9-]+', resource.get('server', '')) or not re.fullmatch(r'https://[^\s]+', resource.get('url', ''))):
|
||||
raise ValueError('Invalid owned MCP record.')
|
||||
if 'clients' in resource and (not isinstance(resource['clients'], list) or any(client not in ('codex', 'claude') for client in resource['clients'])):
|
||||
raise ValueError('Invalid shared extension clients.')
|
||||
|
||||
def save(self):
|
||||
if not self.dry_run:
|
||||
@@ -414,7 +416,7 @@ class Manager:
|
||||
self.state['resources'].append(dict(client='codex', name=entry['name'], kind='qmd', package=package))
|
||||
self.save()
|
||||
|
||||
def ensure_mcporter(self, entry):
|
||||
def ensure_mcporter(self, entry, clients):
|
||||
server, url = entry['mcporter_name'], entry['mcporter_url']
|
||||
if not re.fullmatch(r'[a-z0-9-]+', server) or not re.fullmatch(r'https://[^\s]+', url):
|
||||
raise ValueError('Invalid MCPorter server definition.')
|
||||
@@ -434,11 +436,14 @@ class Manager:
|
||||
return
|
||||
if existing is not None and record is not None and existing.get('baseUrl') != record['url']:
|
||||
raise ValueError('Owned MCPorter server changed locally; refusing to overwrite it.')
|
||||
if record is not None:
|
||||
record['clients'] = sorted(set(record.get('clients', ['claude', 'codex'])) | set(clients))
|
||||
self.save()
|
||||
if record is not None and existing is not None and not self.update:
|
||||
return
|
||||
self.command(['mcporter', '--config', str(self.home / '.mcporter/mcporter.json'), 'config', 'add', server, url])
|
||||
if record is None:
|
||||
self.state['resources'].append(dict(client='shared', name=entry['name'], kind='mcp', server=server, url=url))
|
||||
self.state['resources'].append(dict(client='shared', name=entry['name'], kind='mcp', server=server, url=url, clients=sorted(clients)))
|
||||
else:
|
||||
record.update(server=server, url=url)
|
||||
self.save()
|
||||
@@ -507,6 +512,12 @@ class Manager:
|
||||
continue
|
||||
selected_entries = mcporter_entries if record['client'] == 'shared' else direct_entries
|
||||
if (action == 'sync' and record['name'] not in selected_entries) or (action == 'remove' and record['name'] in selected):
|
||||
if record['kind'] == 'mcp':
|
||||
remaining = [client for client in record.get('clients', ['claude', 'codex']) if client not in clients]
|
||||
if remaining:
|
||||
record['clients'] = remaining
|
||||
self.save()
|
||||
continue
|
||||
self.remove(record)
|
||||
if action != 'remove':
|
||||
for client in clients:
|
||||
@@ -515,7 +526,7 @@ class Manager:
|
||||
if client == 'codex' and entry.get('codex_plugin') == 'caveman@thinkhome-caveman':
|
||||
self.repair_caveman_windows_hooks()
|
||||
for entry in mcporter_entries.values():
|
||||
self.ensure_mcporter(entry)
|
||||
self.ensure_mcporter(entry, clients)
|
||||
print('EXTENSIONS: reconciliation complete' + (' (dry run)' if self.dry_run else ''))
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user