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 +73 -5
- package/package.json +1 -1
- package/public/index.html +104 -19
- package/public/js/clientView.js +53 -19
- package/public/js/iconPreferences.js +98 -0
- package/public/js/kanbanView.js +25 -42
- package/public/js/logoService.js +392 -0
- package/public/js/main.js +21 -1
- package/public/js/modals.js +344 -24
- package/public/js/popularMcps.js +304 -0
- package/public/js/serverView.js +24 -7
- package/public/mcp-logos.json +26 -0
- package/public/popular-mcps.json +555 -0
- package/public/style.css +617 -1
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
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
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
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="
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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">×</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>
|
package/public/js/clientView.js
CHANGED
|
@@ -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
|
-
<
|
|
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')
|
|
157
|
-
document.getElementById('selectAllServersBtn')
|
|
158
|
-
document.getElementById('deselectAllServersBtn')
|
|
176
|
+
const deleteSelectedBtn = document.getElementById('deleteSelectedBtn');
|
|
177
|
+
const selectAllServersBtn = document.getElementById('selectAllServersBtn');
|
|
178
|
+
const deselectAllServersBtn = document.getElementById('deselectAllServersBtn');
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
}
|
package/public/js/kanbanView.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
229
|
-
|
|
230
|
-
const
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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, '"')}">${configIndicator.icon}</span>`
|
|
438
416
|
: '';
|
|
439
417
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
const logoHtml =
|
|
443
|
-
|
|
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
|
|