imcp 0.0.10 → 0.0.12

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.
Files changed (41) hide show
  1. package/README.md +4 -0
  2. package/dist/cli/commands/serve.js +2 -1
  3. package/dist/cli/commands/uninstall.js +14 -6
  4. package/dist/core/ConfigurationLoader.d.ts +1 -1
  5. package/dist/core/ConfigurationLoader.js +25 -4
  6. package/dist/core/ConfigurationProvider.d.ts +2 -1
  7. package/dist/core/ConfigurationProvider.js +69 -5
  8. package/dist/core/MCPManager.d.ts +1 -1
  9. package/dist/core/MCPManager.js +28 -6
  10. package/dist/core/ServerSchemaLoader.d.ts +11 -0
  11. package/dist/core/ServerSchemaLoader.js +43 -0
  12. package/dist/core/ServerSchemaProvider.d.ts +17 -0
  13. package/dist/core/ServerSchemaProvider.js +115 -0
  14. package/dist/core/constants.d.ts +11 -1
  15. package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
  16. package/dist/core/types.d.ts +6 -0
  17. package/dist/services/ServerService.d.ts +7 -1
  18. package/dist/services/ServerService.js +24 -9
  19. package/dist/utils/osUtils.js +1 -1
  20. package/dist/web/public/css/detailsWidget.css +110 -0
  21. package/dist/web/public/css/modal.css +42 -0
  22. package/dist/web/public/css/serverDetails.css +42 -30
  23. package/dist/web/public/js/detailsWidget.js +225 -12
  24. package/dist/web/public/js/modal.js +93 -29
  25. package/dist/web/public/js/notifications.js +34 -35
  26. package/dist/web/server.js +13 -2
  27. package/package.json +1 -1
  28. package/src/cli/commands/serve.ts +4 -3
  29. package/src/cli/commands/uninstall.ts +16 -6
  30. package/src/core/ConfigurationLoader.ts +25 -4
  31. package/src/core/ConfigurationProvider.ts +75 -6
  32. package/src/core/MCPManager.ts +34 -9
  33. package/src/core/constants.ts +12 -2
  34. package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
  35. package/src/core/types.ts +7 -1
  36. package/src/services/ServerService.ts +26 -8
  37. package/src/utils/osUtils.ts +1 -1
  38. package/src/web/public/css/modal.css +42 -0
  39. package/src/web/public/js/modal.js +93 -29
  40. package/src/web/public/js/notifications.js +34 -35
  41. package/src/web/server.ts +21 -3
@@ -0,0 +1,110 @@
1
+ .details-widget {
2
+ transition: all 0.3s ease-in-out;
3
+ }
4
+
5
+ .tools-grid {
6
+ display: grid;
7
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
8
+ gap: 0.5rem;
9
+ padding: 0.5rem;
10
+ }
11
+
12
+ .tool-card {
13
+ transition: all 0.3s ease-out;
14
+ border: 1px solid #e5e7eb;
15
+ margin-bottom: 0.5rem;
16
+ padding: 0.75rem;
17
+ }
18
+
19
+ .tool-card.active {
20
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
21
+ border-left: 4px solid #3b82f6;
22
+ background-color: #f8fafc;
23
+ }
24
+
25
+ .tool-card-header {
26
+ position: relative;
27
+ padding-right: 2rem;
28
+ }
29
+
30
+ .tool-card-header::after {
31
+ content: '';
32
+ position: absolute;
33
+ right: 1rem;
34
+ top: 50%;
35
+ transform: translateY(-50%);
36
+ width: 12px;
37
+ height: 12px;
38
+ border-right: 2px solid #64748b;
39
+ border-bottom: 2px solid #64748b;
40
+ transform-origin: 75% 75%;
41
+ transition: transform 0.3s ease;
42
+ }
43
+
44
+ .tool-card.active .tool-card-header::after {
45
+ transform: translateY(-50%) rotate(45deg);
46
+ }
47
+
48
+ .tool-details {
49
+ max-height: 0;
50
+ opacity: 0;
51
+ overflow: hidden;
52
+ transition: all 0.3s ease-out;
53
+ }
54
+
55
+ .tool-details.visible {
56
+ max-height: 1500px;
57
+ opacity: 1;
58
+ padding-top: 0.75rem;
59
+ margin-top: 0.75rem;
60
+ border-top: 1px solid #e5e7eb;
61
+ }
62
+
63
+ .property-item {
64
+ margin-bottom: 0.75rem;
65
+ padding-left: 0.75rem;
66
+ border-left: 2px solid #e5e7eb;
67
+ transition: border-color 0.2s ease;
68
+ font-size: 0.9rem;
69
+ }
70
+
71
+ .property-item:hover {
72
+ border-left-color: #3b82f6;
73
+ }
74
+
75
+ .property-header {
76
+ margin-bottom: 0.5rem;
77
+ }
78
+
79
+ .property-name {
80
+ font-weight: 600;
81
+ color: #1e293b;
82
+ }
83
+
84
+ .property-type {
85
+ color: #64748b;
86
+ font-size: 0.875rem;
87
+ }
88
+
89
+ .property-desc {
90
+ color: #475569;
91
+ font-size: 0.8125rem;
92
+ line-height: 1.4;
93
+ margin-top: 0.25rem;
94
+ }
95
+
96
+ .required-fields {
97
+ background-color: #fef3c7;
98
+ border-left: 4px solid #f59e0b;
99
+ padding: 0.5rem 0.75rem;
100
+ margin-bottom: 1rem;
101
+ font-size: 0.875rem;
102
+ }
103
+
104
+ .nested-properties {
105
+ margin-left: 0.75rem;
106
+ padding-left: 0.75rem;
107
+ border-left: 1px solid #e5e7eb;
108
+ margin-top: 0.5rem;
109
+ font-size: 0.875rem;
110
+ }
@@ -160,6 +160,15 @@ body {
160
160
  transition: all 0.2s ease;
161
161
  cursor: pointer;
162
162
  user-select: none;
163
+ gap: 0.5rem;
164
+ }
165
+
166
+ /* Client actions container */
167
+ .client-actions {
168
+ display: flex;
169
+ align-items: center;
170
+ gap: 0.5rem;
171
+ margin-left: auto;
163
172
  }
164
173
 
165
174
  .client-item:hover {
@@ -180,6 +189,14 @@ body {
180
189
  gap: 0.75rem;
181
190
  }
182
191
 
192
+ /* Status container */
193
+ .status-container {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 0.5rem;
197
+ margin-left: auto;
198
+ }
199
+
183
200
  /* Status badges */
184
201
  .status-badge {
185
202
  display: inline-flex;
@@ -227,6 +244,31 @@ body {
227
244
  background-color: #fef3c7;
228
245
  }
229
246
 
247
+ /* Uninstall button styling */
248
+ .uninstall-btn {
249
+ display: inline-flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ width: 28px;
253
+ height: 28px;
254
+ border-radius: 6px;
255
+ border: 1px solid transparent;
256
+ background: transparent;
257
+ cursor: pointer;
258
+ transition: all 0.2s ease;
259
+ padding: 0;
260
+ }
261
+
262
+ .uninstall-btn:hover {
263
+ background-color: #fee2e2;
264
+ border-color: #ef4444;
265
+ transform: scale(1.05);
266
+ }
267
+
268
+ .uninstall-btn i {
269
+ font-size: 1.25rem;
270
+ }
271
+
230
272
  /* Environment variables section */
231
273
  #modalEnvInputs input {
232
274
  width: 100%;
@@ -6,16 +6,14 @@
6
6
  }
7
7
 
8
8
  .server-item-content {
9
+ position: relative;
9
10
  border: 1px solid #e5e7eb;
10
11
  border-radius: 0.5rem;
11
12
  padding: 1rem;
13
+ padding-right: calc(120px + 3rem); /* Button width + spacing */
12
14
  margin-bottom: 1rem;
13
15
  background-color: #ffffff;
14
16
  transition: all 0.2s ease;
15
- position: relative;
16
- display: flex;
17
- justify-content: space-between;
18
- align-items: bottom; /* Center items vertically */
19
17
  }
20
18
 
21
19
  .server-item:hover .server-item-content {
@@ -38,8 +36,9 @@
38
36
  }
39
37
 
40
38
  .details-widget.expanded {
41
- max-height: 500px; /* Adjust based on content */
39
+ max-height: 800px; /* Increased height to accommodate more content */
42
40
  border-color: #3b82f6;
41
+ transition: max-height 0.3s ease-in-out;
43
42
  }
44
43
 
45
44
  .details-widget-content {
@@ -59,40 +58,53 @@
59
58
  border-color: #3b82f6;
60
59
  }
61
60
 
62
- /* Server item content layout */
61
+ /* Server item layout */
63
62
  .server-item-info {
64
- flex: 1;
65
- padding-right: 1rem;
66
- min-width: 0; /* Prevent content from overflowing */
67
- }
68
-
69
- /* Install/Uninstall buttons positioning */
70
- .action-buttons {
71
- flex-shrink: 0;
72
- display: flex;
73
- align-items: center;
74
- margin-left: 1rem; /* Add some space between content and button */
63
+ width: 100%;
75
64
  }
76
65
 
77
- /* Ensure buttons stay in place on hover */
78
- .server-item:hover .action-buttons {
79
- position: relative;
80
- z-index: 2;
66
+ .server-item-header {
67
+ margin-bottom: 1rem;
81
68
  }
82
69
 
83
- /* Button styles */
84
- .action-buttons button {
85
- white-space: nowrap;
86
- padding: 0.5rem 1rem; /* Slightly larger padding for better visibility */
87
- min-width: 80px; /* Ensure consistent button width */
88
- text-align: center;
70
+ .server-item-header h5 {
71
+ margin-bottom: 0.5rem;
89
72
  }
90
73
 
91
- /* Status badges layout */
92
- .server-item-info .flex-wrap {
74
+ /* Client status section */
75
+ .flex-wrap {
93
76
  margin: -0.25rem; /* Negative margin to offset badge spacing */
94
77
  }
95
78
 
96
- .server-item-info .flex-wrap > * {
79
+ .flex-wrap > * {
97
80
  margin: 0.25rem; /* Even spacing between badges */
81
+ }
82
+
83
+ /* Install/Uninstall button section */
84
+ .action-buttons {
85
+ position: absolute;
86
+ right: 1rem;
87
+ top: 50%;
88
+ transform: translateY(-50%);
89
+ margin: 0;
90
+ }
91
+
92
+ .action-buttons button {
93
+ min-width: 120px;
94
+ padding: 0.5rem 1.5rem;
95
+ text-align: center;
96
+ font-weight: 600;
97
+ transition: all 0.15s ease;
98
+ white-space: nowrap;
99
+ }
100
+
101
+ /* Status badges */
102
+ .server-item-info .flex-wrap span {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ padding: 0.375rem 0.75rem;
106
+ border-radius: 9999px;
107
+ font-size: 0.75rem;
108
+ line-height: 1;
109
+ white-space: nowrap;
98
110
  }
@@ -2,44 +2,257 @@ export class DetailsWidget {
2
2
  constructor(container) {
3
3
  this.container = container;
4
4
  this.isExpanded = false;
5
+ this.expandedTool = null;
5
6
  this.init();
6
7
  }
7
8
 
8
9
  init() {
9
- // Create details widget elements
10
+ // Create and append stylesheet
11
+ const link = document.createElement('link');
12
+ link.rel = 'stylesheet';
13
+ link.href = '/css/detailsWidget.css';
14
+ document.head.appendChild(link);
15
+
10
16
  this.widgetElement = document.createElement('div');
11
17
  this.widgetElement.className = 'details-widget';
18
+ this.widgetElement.style.overflow = 'auto'; // Enable scrolling
12
19
  this.contentElement = document.createElement('div');
13
20
  this.contentElement.className = 'details-widget-content';
14
21
  this.widgetElement.appendChild(this.contentElement);
15
22
  this.container.appendChild(this.widgetElement);
16
23
  }
17
24
 
18
- setContent(description) {
19
- this.contentElement.innerHTML = `
20
- <div class="description-text">
21
- ${description || 'No description available.'}
22
- </div>
23
- `;
25
+ setContent(content) {
26
+ if (typeof content === 'string') {
27
+ this.contentElement.innerHTML = `
28
+ <div class="description-text">
29
+ ${content || 'No description available.'}
30
+ </div>
31
+ `;
32
+ return;
33
+ }
34
+
35
+ // Handle direct schema object or wrapped schema object
36
+ if (typeof content === 'object') {
37
+ const schemaData = content.schema || content; // Handle both {schema: data} and direct data
38
+ this.renderToolsList(schemaData);
39
+ return;
40
+ }
41
+
42
+ this.contentElement.innerHTML = '<p>Invalid content format</p>';
24
43
  }
25
44
 
45
+ // Maintain compatibility with old toggle behavior
26
46
  toggle() {
27
- this.isExpanded = !this.isExpanded;
28
47
  if (this.isExpanded) {
29
- this.expand();
30
- } else {
31
48
  this.collapse();
49
+ } else {
50
+ this.expand();
32
51
  }
33
52
  }
34
53
 
35
54
  expand() {
55
+ this.isExpanded = true;
36
56
  this.widgetElement.classList.add('expanded');
37
- this.container.querySelector('.server-item-content').classList.add('expanded');
57
+ if (this.container.querySelector('.server-item-content')) {
58
+ this.container.querySelector('.server-item-content').classList.add('expanded');
59
+ }
38
60
  }
39
61
 
40
62
  collapse() {
63
+ this.isExpanded = false;
41
64
  this.widgetElement.classList.remove('expanded');
42
- this.container.querySelector('.server-item-content').classList.remove('expanded');
65
+ if (this.container.querySelector('.server-item-content')) {
66
+ this.container.querySelector('.server-item-content').classList.remove('expanded');
67
+ }
68
+ // Reset expanded tool state
69
+ this.expandedTool = null;
70
+ }
71
+
72
+ renderToolsList(schemaContent) {
73
+ if (!schemaContent || typeof schemaContent !== 'object') {
74
+ this.contentElement.innerHTML = '<p>Invalid schema format</p>';
75
+ return;
76
+ }
77
+
78
+ let html = `
79
+ <div class="tools-list p-2">
80
+ <div class="tools-grid grid gap-2">
81
+ `;
82
+
83
+ Object.entries(schemaContent).forEach(([toolName, toolInfo]) => {
84
+ if (!toolInfo) return;
85
+
86
+ html += `
87
+ <div class="tool-card" data-tool="${this.escapeHtml(toolName)}">
88
+ <div class="tool-card-header">
89
+ <div class="tool-header mb-2">
90
+ <h3 class="text-md font-semibold text-blue-600">${toolInfo.name || toolName}</h3>
91
+ </div>
92
+ <p class="text-gray-700 text-sm">${toolInfo.description || 'No description available'}</p>
93
+ </div>
94
+ <div class="tool-details hidden mt-3" data-tool-details="${this.escapeHtml(toolName)}">
95
+ ${this.renderInputSchema(toolInfo.inputSchema)}
96
+ </div>
97
+ </div>
98
+ `;
99
+ });
100
+
101
+ html += `
102
+ </div>
103
+ </div>
104
+ `;
105
+
106
+ this.contentElement.innerHTML = html;
107
+
108
+ // Add event listeners to tool cards
109
+ this.contentElement.querySelectorAll('.tool-card').forEach(card => {
110
+ const header = card.querySelector('.tool-card-header');
111
+ header.addEventListener('click', (e) => {
112
+ e.stopPropagation(); // Prevent click from bubbling up to server-item
113
+ const toolName = card.dataset.tool;
114
+ this.toggleToolDetails(toolName, card);
115
+ });
116
+
117
+ // Add click handler for the details area to prevent bubbling
118
+ const details = card.querySelector('.tool-details');
119
+ details.addEventListener('click', (e) => {
120
+ e.stopPropagation(); // Prevent click from bubbling up
121
+ });
122
+ });
123
+ }
124
+
125
+ toggleToolDetails(toolName, card) {
126
+ const detailsElement = card.querySelector(`[data-tool-details="${toolName}"]`);
127
+ const isExpanded = !detailsElement.classList.contains('hidden');
128
+
129
+ // Collapse all other expanded tools
130
+ if (this.expandedTool && this.expandedTool !== toolName) {
131
+ const prevCard = this.contentElement.querySelector(`[data-tool="${this.expandedTool}"]`);
132
+ if (prevCard) {
133
+ const prevDetails = prevCard.querySelector(`[data-tool-details="${this.expandedTool}"]`);
134
+ prevDetails?.classList.add('hidden');
135
+ prevCard.classList.remove('active');
136
+ setTimeout(() => {
137
+ prevDetails.classList.remove('visible');
138
+ }, 0);
139
+ }
140
+ }
141
+
142
+ // Toggle current tool with animation
143
+ if (isExpanded) {
144
+ detailsElement.classList.remove('visible');
145
+ card.classList.remove('active');
146
+ setTimeout(() => {
147
+ detailsElement.classList.add('hidden');
148
+ }, 300); // Match transition duration from CSS
149
+ this.expandedTool = null;
150
+ } else {
151
+ detailsElement.classList.remove('hidden');
152
+ card.classList.add('active');
153
+ setTimeout(() => {
154
+ detailsElement.classList.add('visible');
155
+ }, 0);
156
+ this.expandedTool = toolName;
157
+ detailsElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
158
+ }
159
+ }
160
+
161
+ renderInputSchema(schema) {
162
+ if (!schema || typeof schema !== 'object') {
163
+ return '<p>No input schema available</p>';
164
+ }
165
+
166
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
167
+ return '<p>No input properties defined</p>';
168
+ }
169
+
170
+ let html = '<div class="properties-list">';
171
+
172
+ try {
173
+ // Required fields banner
174
+ if (schema.required?.length > 0) {
175
+ html += `
176
+ <div class="required-fields mb-3 bg-yellow-50 p-2 rounded">
177
+ <span class="text-sm font-medium text-yellow-800">
178
+ Required: ${schema.required.join(', ')}
179
+ </span>
180
+ </div>
181
+ `;
182
+ }
183
+
184
+ // Render each property
185
+ Object.entries(schema.properties).forEach(([propName, propDetails]) => {
186
+ if (!propDetails) return;
187
+
188
+ const isRequired = schema.required?.includes(propName);
189
+ const type = this.getPropertyType(propDetails);
190
+
191
+ html += `
192
+ <div class="property-item ${isRequired ? 'required' : ''}">
193
+ <div class="property-header">
194
+ <span class="property-name">${this.escapeHtml(propName)}</span>
195
+ <span class="property-type">${this.escapeHtml(type)}</span>
196
+ ${isRequired ? '<span class="required-badge">Required</span>' : ''}
197
+ </div>
198
+ ${propDetails.description ?
199
+ `<p class="property-desc">${this.escapeHtml(propDetails.description)}</p>` : ''}
200
+ ${propDetails.default !== undefined ?
201
+ `<p class="property-default">Default: ${this.escapeHtml(JSON.stringify(propDetails.default))}</p>` : ''}
202
+ ${this.renderNestedProperties(propDetails)}
203
+ </div>
204
+ `;
205
+ });
206
+
207
+ return html + '</div>';
208
+ } catch (error) {
209
+ console.error('Error rendering input schema:', error);
210
+ return '<p>Error rendering input schema</p>';
211
+ }
212
+ }
213
+
214
+ renderNestedProperties(propDetails) {
215
+ if (propDetails.properties) {
216
+ let html = '<div class="nested-properties">';
217
+ Object.entries(propDetails.properties).forEach(([name, details]) => {
218
+ const type = this.getPropertyType(details);
219
+ html += `
220
+ <div class="nested-property-item">
221
+ <div class="property-header">
222
+ <span class="property-name">${this.escapeHtml(name)}</span>
223
+ <span class="property-type">${this.escapeHtml(type)}</span>
224
+ </div>
225
+ ${details.description ?
226
+ `<p class="property-desc">${this.escapeHtml(details.description)}</p>` : ''}
227
+ </div>
228
+ `;
229
+ });
230
+ return html + '</div>';
231
+ }
232
+ return '';
233
+ }
234
+
235
+ getPropertyType(propDetails) {
236
+ if (propDetails.anyOf) {
237
+ return `oneOf: [${propDetails.anyOf.map(type => type.type || 'any').join(', ')}]`;
238
+ }
239
+ if (propDetails.type === 'array') {
240
+ const itemType = propDetails.items?.type || 'any';
241
+ return `array<${itemType}>`;
242
+ }
243
+ return propDetails.type || 'any';
244
+ }
245
+
246
+ escapeHtml(unsafe) {
247
+ if (unsafe === undefined || unsafe === null) {
248
+ return '';
249
+ }
250
+ return String(unsafe)
251
+ .replace(/&/g, "&amp;")
252
+ .replace(/</g, "&lt;")
253
+ .replace(/>/g, "&gt;")
254
+ .replace(/"/g, "&quot;")
255
+ .replace(/'/g, "&#039;");
43
256
  }
44
257
 
45
258
  isVisible() {