mcp-config-manager 1.0.13 → 2.1.0

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.
package/README.md CHANGED
@@ -9,12 +9,15 @@ Simple CLI and web UI tool to manage Model Context Protocol (MCP) configurations
9
9
  - **Auto-detection**: Automatically discovers supported clients with existing MCP configuration files.
10
10
  - **Multi-client support**: Manage configs for a growing list of clients, including Claude, VS Code, Cursor, Windsurf, Gemini, and more. The tool can detect any client that follows the MCP specification.
11
11
  - **Simple CLI**: Command-line interface for quick operations
12
- - **Web UI**: Clean web interface with List and Kanban views
12
+ - **Web UI**: Clean web interface with List, Kanban, and Server views
13
+ - **MCP Directory**: Browse and add popular MCP servers from curated sources (mcpservers.org, awesome-remote-mcp-servers)
13
14
  - **Remote MCP Support**: Native HTTP and SSE transport support for remote MCP servers
14
- - **Kanban Board**: Drag-and-drop servers between clients with focus mode
15
+ - **Kanban Board**: Drag-and-drop servers between clients with rainbow-colored cards and focus mode
15
16
  - **Focus Mode**: Click any server to highlight it across all clients with bulk actions
16
17
  - **Config Differentiation**: Visual indicators show when same server has different configs across clients
17
- - **MCP Logos**: Automatic logo loading for popular MCP servers (GitHub, Slack, Postgres, etc.)
18
+ - **Auto-Icon Detection**: Automatic logo loading via logo.dev API - matches by name, npm package, or domain
19
+ - **Custom Icon Editing**: Click any server icon to customize - search logos, use custom URL, or disable
20
+ - **Dark Mode**: Full dark theme support with properly adapted colors
18
21
  - **JSON Editor**: Edit server configs as raw JSON or using forms
19
22
  - **Bulk Operations**: Copy servers to multiple clients at once
20
23
  - **Clipboard Support**: Quick copy server configs to clipboard
@@ -127,8 +130,8 @@ The web UI provides:
127
130
  - Remove from all clients button for each server
128
131
  - Bulk action toolbar with select all/none options
129
132
  - **Kanban View**: Drag-and-drop servers between clients
133
+ - Rainbow-colored cards for visual distinction (adapts to dark mode)
130
134
  - Full functionality on cards (edit, copy, export, delete)
131
- - Visual icons for quick actions
132
135
  - Drag to copy servers between clients
133
136
  - **Focus Mode**: Click a server card to enter focus mode
134
137
  - Highlights the selected server across all clients
@@ -139,10 +142,24 @@ The web UI provides:
139
142
  - 🔗 Link icon: Identical config across all clients
140
143
  - ⚠️ Warning icon: Config or env vars differ between clients
141
144
  - Hover for detailed diff tooltip
142
- - **MCP Logos**: Automatic logo display for known servers (GitHub, Postgres, Slack, etc.)
145
+ - **Auto-Icons**: Automatic logo detection for all servers via logo.dev
146
+ - High-quality logos from logo.dev API
147
+ - Matches known services from curated database
148
+ - Extracts service name from npm packages
149
+ - Shows initials when no logo found
150
+ - **Custom Icons**: Click any icon to customize
151
+ - Search logo.dev by company/domain name
152
+ - Use custom image URL
153
+ - Set to auto-detect or disable icon
143
154
  - **Server View**: Consolidated view of all servers across clients
144
155
  - Shows which clients each server is configured for
145
156
  - Bulk operations to add servers to multiple clients
157
+ - **MCP Directory**: Browse and discover popular MCP servers
158
+ - Curated list from mcpservers.org and awesome-remote-mcp-servers
159
+ - Filter by category, auth type, and search
160
+ - One-click add to multiple clients
161
+ - Shows auth requirements (No Auth, API Key, OAuth)
162
+ - Credits to [jaw9c](https://github.com/jaw9c/awesome-remote-mcp-servers)
146
163
  - **Remote MCP Support**: Native transport type support for remote MCP servers
147
164
  - Choose between HTTP (Streamable HTTP) or SSE (Server-Sent Events) transport
148
165
  - Simply provide server name, transport type, and remote URL
@@ -152,6 +169,10 @@ The web UI provides:
152
169
  - **Multi-select Copy**: Copy servers to multiple clients at once
153
170
  - **Clipboard Support**: Quick copy server configs with one click
154
171
  - **Config Path Display**: See where each client's config file is located
172
+ - **Dark Mode**: Full dark theme support with toggle in header
173
+ - Properly adapted colors for all views
174
+ - Rainbow cards use jewel-tones in dark mode
175
+ - Persists preference in localStorage
155
176
  - Visual overview of all clients
156
177
  - Add/edit/delete servers
157
178
  - Import/export configurations
@@ -315,6 +336,53 @@ npm run web
315
336
 
316
337
  This allows testing functionality without affecting real MCP client configurations.
317
338
 
339
+ ## What's New in v2.1.0
340
+
341
+ ### Custom Icon Editing
342
+ Click any server icon to customize it:
343
+ - **Search logos** by company name or domain via logo.dev API
344
+ - **Custom URL** support for any image
345
+ - **Auto-detect** option uses smart matching
346
+ - **No icon** option shows initials only
347
+ - Preferences saved in localStorage
348
+
349
+ ### Improved Logo Quality
350
+ - Upgraded to logo.dev API for high-quality company logos
351
+ - Better service name extraction from npm packages
352
+ - Smarter domain matching with skip list for ambiguous names
353
+
354
+ ### UI Improvements
355
+ - Icons properly aligned with server names
356
+ - Copy button moved to header actions (cleaner card layout)
357
+ - Simplified icon editor UI in modals
358
+
359
+ ---
360
+
361
+ ## What's New in v2.0.0
362
+
363
+ ### MCP Directory
364
+ Browse and add popular MCP servers from curated sources without manual configuration:
365
+ - **50+ servers** from mcpservers.org and awesome-remote-mcp-servers
366
+ - **Filter by category**: Development, Productivity, Data, Communication, etc.
367
+ - **Filter by auth type**: No Auth, API Key, OAuth
368
+ - **One-click install** to multiple clients
369
+
370
+ ### Auto-Icon Detection
371
+ All servers now automatically display logos:
372
+ - Matches from curated logo database (GitHub, Slack, Notion, etc.)
373
+ - Extracts service names from npm packages (`@modelcontextprotocol/server-github` → GitHub logo)
374
+ - High-quality logos via logo.dev API
375
+ - Shows initials when no logo found
376
+
377
+ ### Dark Mode Improvements
378
+ - Rainbow-colored Kanban cards now use jewel-tones in dark mode
379
+ - Proper text contrast on all colored backgrounds
380
+ - Full theme support across all views and modals
381
+
382
+ ### Bug Fixes
383
+ - Fixed server deletion in Server View (was prompting twice but not deleting)
384
+ - Improved event handling reliability
385
+
318
386
  ## License
319
387
 
320
388
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-config-manager",
3
- "version": "1.0.13",
3
+ "version": "2.1.0",
4
4
  "description": "Simple CLI and web UI to manage MCP configs across multiple AI clients",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -16,6 +16,9 @@
16
16
  <button id="kanbanViewBtn" class="view-btn">Kanban View</button>
17
17
  <button id="serverViewBtn" class="view-btn">Server View</button>
18
18
  </div>
19
+ <div class="header-actions">
20
+ <button id="browsePopularMcpsBtn" class="btn btn-primary">MCP Directory</button>
21
+ </div>
19
22
  <div class="theme-switcher-container">
20
23
  <label for="themeSwitcher" class="theme-switcher-label">Dark Mode</label>
21
24
  <label class="theme-switcher" for="themeSwitcher">
@@ -69,19 +72,29 @@
69
72
  </div>
70
73
 
71
74
  <div id="kanbanViewContainer" style="display: none;">
72
- <div class="sort-options">
73
- <label for="kanbanSort">Sort by:</label>
74
- <select id="kanbanSort">
75
- <option value="name-asc">Name (A-Z)</option>
76
- <option value="name-desc">Name (Z-A)</option>
77
- <option value="servers-asc">Servers (Low to High)</option>
78
- <option value="servers-desc">Servers (High to Low)</option>
79
- </select>
75
+ <div class="kanban-header">
76
+ <div class="sort-options">
77
+ <label for="kanbanSort">Sort by:</label>
78
+ <select id="kanbanSort">
79
+ <option value="name-asc">Name (A-Z)</option>
80
+ <option value="name-desc">Name (Z-A)</option>
81
+ <option value="servers-asc">Servers (Low to High)</option>
82
+ <option value="servers-desc">Servers (High to Low)</option>
83
+ </select>
84
+ </div>
85
+ <div class="kanban-actions">
86
+ <button id="kanbanAddServerBtn" class="btn btn-primary">+ Add Server</button>
87
+ </div>
80
88
  </div>
81
89
  </div>
82
90
 
83
91
  <div id="serverViewContainer" style="display: none;">
84
- <h2>All Servers</h2>
92
+ <div class="server-view-header">
93
+ <h2>All Servers</h2>
94
+ <div class="server-view-actions">
95
+ <button id="serverViewAddServerBtn" class="btn btn-primary">+ Add Server</button>
96
+ </div>
97
+ </div>
85
98
  <div class="server-list-all" id="allServersList"></div>
86
99
  </div>
87
100
  </div>
@@ -99,11 +112,18 @@
99
112
  <div id="formTab" class="tab-content active">
100
113
  <div class="form-group">
101
114
  <label for="serverName">Server Name</label>
102
- <input type="text" id="serverName" name="serverName" required>
115
+ <div class="server-name-row">
116
+ <input type="text" id="serverName" name="serverName" required>
117
+ <div class="icon-edit-trigger" id="iconEditTrigger" title="Edit icon">
118
+ <div class="icon-preview-mini" id="iconPreviewMini"></div>
119
+ <svg class="pencil-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
120
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
121
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
122
+ </svg>
123
+ </div>
124
+ </div>
103
125
  </div>
104
126
 
105
-
106
-
107
127
  <div class="form-group">
108
128
  <label for="serverCommand">Command</label>
109
129
  <input type="text" id="serverCommand" name="serverCommand" placeholder="e.g., npx">
@@ -327,12 +347,77 @@
327
347
  </div>
328
348
  </div>
329
349
 
330
- <script type="module" src="js/utils.js?v=3"></script>
331
- <script type="module" src="js/api.js?v=3"></script>
332
- <script type="module" src="js/modals.js?v=3"></script>
333
- <script type="module" src="js/clientView.js?v=3"></script>
334
- <script type="module" src="js/kanbanView.js?v=3"></script>
335
- <script type="module" src="js/serverView.js?v=3"></script>
336
- <script type="module" src="js/main.js?v=3"></script>
350
+ <!-- Select Client for Add Server Modal -->
351
+ <div id="selectClientModal" class="modal" style="display: none;">
352
+ <div class="modal-content">
353
+ <h3>Select Client</h3>
354
+ <p>Choose which client to add the new server to:</p>
355
+ <form id="selectClientForm">
356
+ <div class="form-group">
357
+ <div id="selectClientList" class="checkbox-list client-select-list"></div>
358
+ </div>
359
+ <div class="modal-actions">
360
+ <button type="submit" class="btn btn-primary">Continue</button>
361
+ <button type="button" id="cancelSelectClient" class="btn btn-secondary">Cancel</button>
362
+ </div>
363
+ </form>
364
+ </div>
365
+ </div>
366
+
367
+ <!-- Icon Search Modal -->
368
+ <div id="iconSearchModal" class="modal" style="display: none;">
369
+ <div class="modal-content modal-small">
370
+ <h3>Change Icon</h3>
371
+ <p class="modal-description">Search by domain, paste a URL, or use auto-detect:</p>
372
+ <div class="form-group">
373
+ <input type="text" id="iconSearchInput" placeholder="github.com, https://... or company name">
374
+ </div>
375
+ <div class="icon-search-results" id="iconSearchResults">
376
+ <div class="icon-search-hint">Type a domain or company name and press Enter</div>
377
+ </div>
378
+ <div class="icon-search-suggestions" id="iconSearchSuggestions">
379
+ <div class="suggestions-label">Popular services:</div>
380
+ <div class="suggestions-grid" id="suggestionsGrid"></div>
381
+ </div>
382
+ <div class="modal-actions">
383
+ <button type="button" id="confirmIconSearch" class="btn btn-primary" disabled>Select</button>
384
+ <button type="button" id="cancelIconSearch" class="btn btn-secondary">Cancel</button>
385
+ </div>
386
+ </div>
387
+ </div>
388
+
389
+ <!-- Popular MCPs Modal -->
390
+ <div id="popularMcpsModal" class="modal" style="display: none;">
391
+ <div class="modal-content modal-wide">
392
+ <span class="close" id="closePopularMcps">&times;</span>
393
+ <h3>Browse Popular MCPs</h3>
394
+ <div class="popular-mcps-filters">
395
+ <input type="text" id="popularMcpsSearch" placeholder="Search servers...">
396
+ <select id="popularMcpsCategory">
397
+ <option value="">All Categories</option>
398
+ </select>
399
+ <select id="popularMcpsAuth">
400
+ <option value="">All Auth Types</option>
401
+ <option value="none">No Auth Required</option>
402
+ <option value="api-key">API Key</option>
403
+ <option value="oauth">OAuth</option>
404
+ </select>
405
+ </div>
406
+ <div id="popularMcpsList" class="popular-mcps-grid"></div>
407
+ <div class="popular-mcps-attribution">
408
+ Data from <a href="https://mcpservers.org" target="_blank" rel="noopener">mcpservers.org</a>
409
+ and <a href="https://github.com/jaw9c/awesome-remote-mcp-servers" target="_blank" rel="noopener">awesome-remote-mcp-servers</a> by jaw9c
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ <script type="module" src="js/utils.js?v=4"></script>
415
+ <script type="module" src="js/api.js?v=4"></script>
416
+ <script type="module" src="js/modals.js?v=4"></script>
417
+ <script type="module" src="js/clientView.js?v=4"></script>
418
+ <script type="module" src="js/kanbanView.js?v=4"></script>
419
+ <script type="module" src="js/serverView.js?v=4"></script>
420
+ <script type="module" src="js/popularMcps.js?v=4"></script>
421
+ <script type="module" src="js/main.js?v=4"></script>
337
422
  </body>
338
423
  </html>
@@ -1,9 +1,11 @@
1
1
  import { listClientsApi, getClientConfigApi, deleteServerApi } from './api.js';
2
2
  import { showServerModal, copyServer, copyToClipboard, exportServer, deleteServer, removeFromAll, updateBulkActions, selectAllServers, deselectAllServers, deleteSelected } from './modals.js';
3
+ import { getServerLogo, getInitials } from './logoService.js';
3
4
 
4
5
  let currentClient = null;
5
6
  let clients = [];
6
7
  let loadClientsCallback = null; // Callback to main.js to reload all clients
8
+ let serverListListenerAttached = false; // Track if event listener is already attached
7
9
 
8
10
  export function initClientView(allClients, currentClientId, loadClientsFn) {
9
11
  clients = allClients;
@@ -124,11 +126,19 @@ function renderServerList(servers) {
124
126
  }
125
127
  }
126
128
 
129
+ const initials = getInitials(name);
127
130
  card.innerHTML = `
128
131
  <div class="server-header">
129
132
  <input type="checkbox" class="server-checkbox" data-server="${name}">
130
- <span class="server-name">${name}</span>
133
+ <div class="server-info">
134
+ <div class="server-logo-container">
135
+ <img src="" class="server-logo" alt="" style="display:none;width:28px;height:28px" data-server="${name}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
136
+ <div class="server-logo-fallback" style="width:28px;height:28px;font-size:11px">${initials}</div>
137
+ </div>
138
+ <span class="server-name">${name}</span>
139
+ </div>
131
140
  <div class="server-actions">
141
+ <button class="icon-btn copy-to-clipboard-btn" data-server-name="${name}" title="Copy to Clipboard">📋</button>
132
142
  <button class="btn btn-small btn-secondary edit-server-btn" data-server-name="${name}">Edit</button>
133
143
  <button class="btn btn-small btn-secondary export-server-btn" data-server-name="${name}">Export</button>
134
144
  <button class="btn btn-small btn-danger delete-server-btn" data-server-name="${name}">Delete</button>
@@ -138,11 +148,21 @@ function renderServerList(servers) {
138
148
  ${transportHtml}
139
149
  ${server.command ? `<div class="detail-row"><strong>Command:</strong> ${server.command}</div>` : ''}
140
150
  ${server.args ? `<div class="detail-row"><strong>Args:</strong> ${server.args.join(' ')}</div>` : ''}
141
- <div class="detail-row"><button class="icon-btn secondary copy-to-clipboard-btn" data-server-name="${name}" title="Copy to Clipboard">📋</button></div>
142
151
  </div>
143
152
  `;
144
153
 
145
154
  serverList.appendChild(card);
155
+
156
+ // Load logo asynchronously
157
+ const imgElement = card.querySelector('.server-logo');
158
+ getServerLogo(name, server).then(logoUrl => {
159
+ if (logoUrl && imgElement) {
160
+ imgElement.src = logoUrl;
161
+ imgElement.style.display = 'block';
162
+ const fallback = imgElement.nextElementSibling;
163
+ if (fallback) fallback.style.display = 'none';
164
+ }
165
+ });
146
166
  }
147
167
 
148
168
  attachClientViewEventListeners();
@@ -153,24 +173,38 @@ function attachClientViewEventListeners() {
153
173
  checkbox.onchange = updateBulkActions;
154
174
  });
155
175
 
156
- document.getElementById('deleteSelectedBtn').onclick = () => deleteSelected(loadClientServers, null, window.loadClients); // Call deleteSelected from modals.js
157
- document.getElementById('selectAllServersBtn').onclick = selectAllServers;
158
- document.getElementById('deselectAllServersBtn').onclick = deselectAllServers;
176
+ const deleteSelectedBtn = document.getElementById('deleteSelectedBtn');
177
+ const selectAllServersBtn = document.getElementById('selectAllServersBtn');
178
+ const deselectAllServersBtn = document.getElementById('deselectAllServersBtn');
159
179
 
160
- const serverList = document.getElementById('serverList');
161
- serverList.addEventListener('click', (e) => {
162
- if (e.target.classList.contains('delete-server-btn')) {
163
- deleteServerFromClientView(e.target.dataset.serverName);
164
- } else if (e.target.classList.contains('edit-server-btn')) {
165
- editServerFromClientView(e.target.dataset.serverName);
166
- } else if (e.target.classList.contains('copy-server-btn')) {
167
- copyServerFromClientView(e.target.dataset.serverName);
168
- } else if (e.target.classList.contains('copy-to-clipboard-btn')) {
169
- copyToClipboardFromClientView(e.target.dataset.serverName, e);
170
- } else if (e.target.classList.contains('export-server-btn')) {
171
- exportServerFromClientView(e.target.dataset.serverName);
172
- }
173
- });
180
+ if (deleteSelectedBtn) {
181
+ deleteSelectedBtn.onclick = () => deleteSelected(loadClientServers, null, window.loadClients);
182
+ }
183
+ if (selectAllServersBtn) {
184
+ selectAllServersBtn.onclick = selectAllServers;
185
+ }
186
+ if (deselectAllServersBtn) {
187
+ deselectAllServersBtn.onclick = deselectAllServers;
188
+ }
189
+
190
+ // Only attach the serverList click listener once to avoid multiple handlers
191
+ if (!serverListListenerAttached) {
192
+ const serverList = document.getElementById('serverList');
193
+ serverList.addEventListener('click', (e) => {
194
+ if (e.target.classList.contains('delete-server-btn')) {
195
+ deleteServerFromClientView(e.target.dataset.serverName);
196
+ } else if (e.target.classList.contains('edit-server-btn')) {
197
+ editServerFromClientView(e.target.dataset.serverName);
198
+ } else if (e.target.classList.contains('copy-server-btn')) {
199
+ copyServerFromClientView(e.target.dataset.serverName);
200
+ } else if (e.target.classList.contains('copy-to-clipboard-btn')) {
201
+ copyToClipboardFromClientView(e.target.dataset.serverName, e);
202
+ } else if (e.target.classList.contains('export-server-btn')) {
203
+ exportServerFromClientView(e.target.dataset.serverName);
204
+ }
205
+ });
206
+ serverListListenerAttached = true;
207
+ }
174
208
  }
175
209
 
176
210
  // Wrapper functions to pass callbacks
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Icon Preferences Service
3
+ * Stores custom icon preferences in localStorage
4
+ *
5
+ * Preference types:
6
+ * - 'auto': Use automatic detection (default behavior)
7
+ * - 'url': Use a custom URL
8
+ * - 'logodev': Use logo.dev with a specific domain
9
+ * - 'none': Show initials only (no icon)
10
+ */
11
+
12
+ const STORAGE_KEY = 'mcp-custom-icons';
13
+
14
+ /**
15
+ * Get all icon preferences from localStorage
16
+ * @returns {object} Map of serverName -> { type, value }
17
+ */
18
+ export function getAllIconPreferences() {
19
+ try {
20
+ const stored = localStorage.getItem(STORAGE_KEY);
21
+ return stored ? JSON.parse(stored) : {};
22
+ } catch (error) {
23
+ console.error('Failed to load icon preferences:', error);
24
+ return {};
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Save all icon preferences to localStorage
30
+ * @param {object} preferences - Map of serverName -> { type, value }
31
+ */
32
+ function saveAllIconPreferences(preferences) {
33
+ try {
34
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
35
+ } catch (error) {
36
+ console.error('Failed to save icon preferences:', error);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get icon preference for a specific server
42
+ * @param {string} serverName - The server name
43
+ * @returns {object|null} { type, value } or null if no custom preference
44
+ */
45
+ export function getIconPreference(serverName) {
46
+ const preferences = getAllIconPreferences();
47
+ return preferences[serverName] || null;
48
+ }
49
+
50
+ /**
51
+ * Set icon preference for a server
52
+ * @param {string} serverName - The server name
53
+ * @param {string} type - 'auto' | 'url' | 'logodev' | 'none'
54
+ * @param {string} value - URL or domain (depending on type)
55
+ */
56
+ export function setIconPreference(serverName, type, value = null) {
57
+ const preferences = getAllIconPreferences();
58
+
59
+ if (type === 'auto') {
60
+ // Remove custom preference to use auto-detection
61
+ delete preferences[serverName];
62
+ } else {
63
+ preferences[serverName] = { type, value };
64
+ }
65
+
66
+ saveAllIconPreferences(preferences);
67
+ }
68
+
69
+ /**
70
+ * Remove icon preference for a server (revert to auto)
71
+ * @param {string} serverName - The server name
72
+ */
73
+ export function removeIconPreference(serverName) {
74
+ const preferences = getAllIconPreferences();
75
+ delete preferences[serverName];
76
+ saveAllIconPreferences(preferences);
77
+ }
78
+
79
+ /**
80
+ * Migrate icon preference when server is renamed
81
+ * @param {string} oldName - Old server name
82
+ * @param {string} newName - New server name
83
+ */
84
+ export function migrateIconPreference(oldName, newName) {
85
+ const preferences = getAllIconPreferences();
86
+ if (preferences[oldName]) {
87
+ preferences[newName] = preferences[oldName];
88
+ delete preferences[oldName];
89
+ saveAllIconPreferences(preferences);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Clear all icon preferences
95
+ */
96
+ export function clearAllIconPreferences() {
97
+ localStorage.removeItem(STORAGE_KEY);
98
+ }
@@ -1,12 +1,12 @@
1
1
  import { getClientConfigApi, copyServerApi, deleteServerApi, getAllServersApi } from './api.js';
2
2
  import { showServerModal, editServer, copyToClipboard, exportServer } from './modals.js';
3
+ import { loadStaticLogos, getServerLogo as getServerLogoAsync, getInitials } from './logoService.js';
3
4
 
4
5
  let clients = [];
5
6
  let loadClientsCallback = null;
6
7
  let listenersAttached = false;
7
8
  let focusedServer = null; // { serverName, clientId }
8
9
  let allServersData = null; // Cache for config comparison
9
- let mcpLogos = null; // Cache for MCP logo database
10
10
 
11
11
  export function initKanbanView(allClients, loadClientsFn) {
12
12
  clients = allClients;
@@ -17,22 +17,10 @@ export function initKanbanView(allClients, loadClientsFn) {
17
17
  listenersAttached = true;
18
18
  }
19
19
  // Load logo database and server data
20
- loadMcpLogos();
20
+ loadStaticLogos();
21
21
  loadAllServersData();
22
22
  }
23
23
 
24
- async function loadMcpLogos() {
25
- try {
26
- const response = await fetch('/mcp-logos.json');
27
- if (response.ok) {
28
- mcpLogos = await response.json();
29
- }
30
- } catch (error) {
31
- console.warn('Could not load MCP logos database:', error);
32
- mcpLogos = {};
33
- }
34
- }
35
-
36
24
  async function loadAllServersData() {
37
25
  try {
38
26
  allServersData = await getAllServersApi();
@@ -225,9 +213,10 @@ function getServerColor(serverName) {
225
213
  }
226
214
  const hue = hash % 360;
227
215
  const isDark = document.body.classList.contains('dark-theme');
228
- // Use lighter backgrounds for better text visibility
229
- const lightness = isDark ? 20 : 92;
230
- const saturation = isDark ? 30 : 50;
216
+ // Dark mode: darker, more saturated colors that work on dark backgrounds
217
+ // Light mode: light pastel backgrounds
218
+ const lightness = isDark ? 25 : 92;
219
+ const saturation = isDark ? 40 : 50;
231
220
  return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
232
221
  }
233
222
 
@@ -367,27 +356,16 @@ function buildConfigDiffTooltip(serverInfo, diffType) {
367
356
  return lines.join('\n');
368
357
  }
369
358
 
370
- function getServerLogo(serverName, server) {
371
- if (!mcpLogos) return null;
372
-
373
- // Check direct match in logo database
374
- const nameLower = serverName.toLowerCase();
375
- if (mcpLogos[nameLower]) return mcpLogos[nameLower];
376
-
377
- // Check common patterns
378
- for (const [key, url] of Object.entries(mcpLogos)) {
379
- if (nameLower.includes(key) || key.includes(nameLower)) {
380
- return url;
359
+ // Sync wrapper that loads logo asynchronously and updates the DOM
360
+ function loadServerLogoAsync(serverName, server, imgElement) {
361
+ getServerLogoAsync(serverName, server).then(logoUrl => {
362
+ if (logoUrl && imgElement && imgElement.parentNode) {
363
+ imgElement.src = logoUrl;
364
+ imgElement.style.display = 'block';
365
+ const fallback = imgElement.nextElementSibling;
366
+ if (fallback) fallback.style.display = 'none';
381
367
  }
382
- }
383
-
384
- // Check npm package name in command/args
385
- if (server.command === 'npx' && server.args?.length > 0) {
386
- const packageName = server.args[0].replace(/^@/, '').split('/').pop().replace(/-mcp.*$/, '');
387
- if (mcpLogos[packageName]) return mcpLogos[packageName];
388
- }
389
-
390
- return null;
368
+ });
391
369
  }
392
370
 
393
371
  function createServerCard(clientId, serverName, server) {
@@ -437,11 +415,12 @@ function createServerCard(clientId, serverName, server) {
437
415
  ? `<span class="config-indicator ${configIndicator.className}" title="${configIndicator.tooltip.replace(/"/g, '&quot;')}">${configIndicator.icon}</span>`
438
416
  : '';
439
417
 
440
- // Get logo
441
- const logoUrl = getServerLogo(serverName, server);
442
- const logoHtml = logoUrl
443
- ? `<img src="${logoUrl}" class="server-logo" alt="" onerror="this.style.display='none'">`
444
- : '';
418
+ // Create logo placeholder with initials fallback
419
+ const initials = getInitials(serverName);
420
+ const logoHtml = `
421
+ <img src="" class="server-logo" alt="" style="display:none" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
422
+ <div class="server-logo-fallback" style="width:24px;height:24px;font-size:10px">${initials}</div>
423
+ `;
445
424
 
446
425
  card.innerHTML = `
447
426
  <div class="kanban-card-header">
@@ -467,6 +446,10 @@ function createServerCard(clientId, serverName, server) {
467
446
  showContextMenu(e, clientId, serverName);
468
447
  });
469
448
 
449
+ // Load logo asynchronously
450
+ const imgElement = card.querySelector('.server-logo');
451
+ loadServerLogoAsync(serverName, server, imgElement);
452
+
470
453
  return card;
471
454
  }
472
455