imcp 0.0.11 → 0.0.13

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 (57) hide show
  1. package/dist/cli/commands/uninstall.js +14 -6
  2. package/dist/core/ConfigurationProvider.d.ts +3 -1
  3. package/dist/core/ConfigurationProvider.js +85 -25
  4. package/dist/core/InstallationService.d.ts +17 -0
  5. package/dist/core/InstallationService.js +127 -61
  6. package/dist/core/MCPManager.d.ts +1 -0
  7. package/dist/core/MCPManager.js +30 -5
  8. package/dist/core/RequirementService.d.ts +4 -4
  9. package/dist/core/RequirementService.js +11 -7
  10. package/dist/core/ServerSchemaLoader.js +2 -2
  11. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  12. package/dist/core/ServerSchemaProvider.js +15 -10
  13. package/dist/core/constants.d.ts +14 -1
  14. package/dist/core/constants.js +4 -1
  15. package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
  16. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  17. package/dist/core/types.d.ts +10 -0
  18. package/dist/services/ServerService.d.ts +12 -1
  19. package/dist/services/ServerService.js +39 -9
  20. package/dist/utils/githubAuth.js +0 -10
  21. package/dist/utils/githubUtils.d.ts +16 -0
  22. package/dist/utils/githubUtils.js +55 -39
  23. package/dist/utils/osUtils.js +1 -1
  24. package/dist/web/public/css/detailsWidget.css +189 -57
  25. package/dist/web/public/css/modal.css +42 -0
  26. package/dist/web/public/css/serverDetails.css +35 -18
  27. package/dist/web/public/index.html +2 -0
  28. package/dist/web/public/js/detailsWidget.js +175 -60
  29. package/dist/web/public/js/modal.js +93 -29
  30. package/dist/web/public/js/notifications.js +34 -35
  31. package/dist/web/public/js/serverCategoryDetails.js +182 -120
  32. package/dist/web/server.js +38 -2
  33. package/package.json +3 -4
  34. package/src/cli/commands/uninstall.ts +16 -6
  35. package/src/core/ConfigurationProvider.ts +102 -25
  36. package/src/core/InstallationService.ts +176 -62
  37. package/src/core/MCPManager.ts +36 -5
  38. package/src/core/RequirementService.ts +12 -8
  39. package/src/core/ServerSchemaLoader.ts +48 -0
  40. package/src/core/ServerSchemaProvider.ts +137 -0
  41. package/src/core/constants.ts +16 -3
  42. package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
  43. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  44. package/src/core/types.ts +11 -1
  45. package/src/services/ServerService.ts +41 -8
  46. package/src/utils/githubAuth.ts +14 -27
  47. package/src/utils/githubUtils.ts +84 -47
  48. package/src/utils/osUtils.ts +1 -1
  49. package/src/web/public/css/detailsWidget.css +235 -0
  50. package/src/web/public/css/modal.css +42 -0
  51. package/src/web/public/css/serverDetails.css +126 -0
  52. package/src/web/public/index.html +2 -0
  53. package/src/web/public/js/detailsWidget.js +264 -0
  54. package/src/web/public/js/modal.js +93 -29
  55. package/src/web/public/js/notifications.js +34 -35
  56. package/src/web/public/js/serverCategoryDetails.js +182 -120
  57. package/src/web/server.ts +52 -3
@@ -273,7 +273,7 @@ export function getPythonPackagePath(pythonExecutablePath: string): string {
273
273
  // Virtual environment structure on Windows: <venv>/Scripts/python.exe
274
274
  const venvRoot = path.dirname(dir);
275
275
  return path.join(venvRoot, 'Lib', 'site-packages');
276
- } else if (dir.toLowerCase().includes('python')) {
276
+ } else {
277
277
  // System Python or Conda on Windows
278
278
  return path.join(dir, 'Lib', 'site-packages');
279
279
  }
@@ -0,0 +1,235 @@
1
+ .details-widget {
2
+ transition: all 0.3s ease-in-out;
3
+ width: 100%;
4
+ max-width: 100%;
5
+ box-sizing: border-box;
6
+ margin: 0;
7
+ padding: 0;
8
+ display: block;
9
+ overflow: hidden;
10
+ }
11
+
12
+ .tools-list {
13
+ width: 100%;
14
+ max-width: 100%;
15
+ margin: 0;
16
+ padding: 0.1rem;
17
+ box-sizing: border-box;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .tool-card {
22
+ width: 100%;
23
+ margin-bottom: -1px;
24
+ border-radius: 0;
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ .tool-card:first-child {
29
+ border-top-left-radius: 6px;
30
+ border-top-right-radius: 6px;
31
+ }
32
+
33
+ .tool-card:last-child {
34
+ border-bottom-left-radius: 6px;
35
+ border-bottom-right-radius: 6px;
36
+ margin-bottom: 0;
37
+ }
38
+
39
+ .tool-card {
40
+ transition: all 0.3s ease-out;
41
+ border: 1px solid #e5e7eb;
42
+ padding: 0.5rem;
43
+ background-color: white;
44
+ position: relative;
45
+ z-index: 1;
46
+ font-size: 0.9rem;
47
+ width: 100%;
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ .tool-card .text-gray-600 {
52
+ font-size: 0.75rem;
53
+ line-height: 1.3;
54
+ }
55
+
56
+ .tool-card.active {
57
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
58
+ border-color: transparent;
59
+ background-color: #f8fafc;
60
+ }
61
+
62
+ .tool-card:hover {
63
+ transform: translateY(-1px);
64
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
65
+ }
66
+
67
+ .tool-card-header {
68
+ position: relative;
69
+ padding-right: 2rem;
70
+ width: 100%;
71
+ box-sizing: border-box;
72
+ cursor: pointer;
73
+ }
74
+
75
+ .tool-card-header::after {
76
+ content: '';
77
+ position: absolute;
78
+ right: 1rem;
79
+ top: 50%;
80
+ transform: translateY(-50%);
81
+ width: 12px;
82
+ height: 12px;
83
+ border-right: 2px solid #64748b;
84
+ border-bottom: 2px solid #64748b;
85
+ transform-origin: 75% 75%;
86
+ transition: transform 0.3s ease;
87
+ }
88
+
89
+ .tool-card.active .tool-card-header::after {
90
+ transform: translateY(-50%) rotate(45deg);
91
+ }
92
+
93
+ .tool-details {
94
+ max-height: 0;
95
+ opacity: 0;
96
+ overflow: hidden;
97
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
98
+ background-color: #f9fafb;
99
+ border-radius: 6px;
100
+ width: 100%;
101
+ max-width: 100%;
102
+ box-sizing: border-box;
103
+ }
104
+
105
+ .tool-details.visible {
106
+ max-height: 2000px;
107
+ opacity: 1;
108
+ padding: 0.1rem;
109
+ margin-top: 0.5rem;
110
+ border-top: 1px solid #e5e7eb;
111
+ width: 100%;
112
+ max-width: 100%;
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ .property-item {
117
+ margin-bottom: 0.15rem;
118
+ padding: 0.5rem;
119
+ border-left: 2px solid #e5e7eb;
120
+ transition: all 0.2s ease;
121
+ font-size: 0.8rem;
122
+ background-color: white;
123
+ border-radius: 4px;
124
+ }
125
+
126
+ .property-item:hover {
127
+ border-left-color: #3b82f6;
128
+ }
129
+
130
+ .property-header {
131
+ margin-bottom: 0.25rem;
132
+ }
133
+
134
+ .property-title {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 0.5rem;
138
+ flex-wrap: wrap;
139
+ }
140
+
141
+ .property-name {
142
+ font-weight: 600;
143
+ color: #1e293b;
144
+ font-size: 0.85rem;
145
+ background-color: #f8fafc;
146
+ padding: 0.2rem 0.4rem;
147
+ border-radius: 4px;
148
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
149
+ display: inline-block;
150
+ }
151
+
152
+ .property-type {
153
+ color: #64748b;
154
+ font-size: 0.65rem;
155
+ padding: 0.1rem 0.3rem;
156
+ background: #f1f5f9;
157
+ border-radius: 3px;
158
+ font-family: 'Courier New', monospace;
159
+ margin-left: 0.3rem;
160
+ }
161
+
162
+ .property-desc {
163
+ color: #6b7280;
164
+ font-size: 0.7rem;
165
+ margin-left: 0.5rem;
166
+ display: inline-block;
167
+ font-style: italic;
168
+ }
169
+
170
+ .property-default {
171
+ font-size: 0.75rem;
172
+ color: #6b7280;
173
+ margin-top: 0.15rem;
174
+ }
175
+
176
+ .property-default code {
177
+ background: #f1f5f9;
178
+ padding: 0.2rem 0.4rem;
179
+ border-radius: 4px;
180
+ font-family: 'Courier New', monospace;
181
+ font-size: 0.85rem;
182
+ }
183
+
184
+ .required-fields {
185
+ border-left: 2px solid #64748b;
186
+ padding: 0.25rem 0.5rem;
187
+ margin-bottom: 0.5rem;
188
+ font-size: 0.8rem;
189
+ border-radius: 2px;
190
+ color: #64748b;
191
+ }
192
+ .required-star {
193
+ color: #dc2626;
194
+ margin-left: 2px;
195
+ font-weight: bold;
196
+ }
197
+
198
+
199
+ /* Ensure proper container behavior for the widget */
200
+ .details-widget-container {
201
+ width: 100%;
202
+ max-width: 100%;
203
+ box-sizing: border-box;
204
+ position: relative;
205
+ margin: 0;
206
+ padding: 0;
207
+ overflow: hidden;
208
+ }
209
+
210
+ .nested-properties {
211
+ margin: 0.25rem 0 0.25rem 0.5rem;
212
+ padding-left: 0.5rem;
213
+ border-left: 1px solid #e5e7eb;
214
+ font-size: 0.8rem;
215
+ }
216
+
217
+ .nested-property-item {
218
+ margin-bottom: 0.5rem;
219
+ padding: 0.25rem 0.5rem;
220
+ }
221
+
222
+ .nested-property-item .property-name {
223
+ font-size: 0.85rem;
224
+ }
225
+
226
+ .nested-property-item .property-type {
227
+ font-size: 0.8rem;
228
+ padding: 0.1rem 0.3rem;
229
+ }
230
+
231
+ .nested-property-item .property-desc {
232
+ font-size: 0.8rem;
233
+ margin-top: 0.25rem;
234
+ color: #64748b;
235
+ }
@@ -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%;
@@ -0,0 +1,126 @@
1
+ /* Server item container */
2
+ .server-item-content {
3
+ cursor: pointer;
4
+ position: relative;
5
+ transition: all 0.2s ease;
6
+ border: 1px solid #e5e7eb;
7
+ border-radius: 0.5rem;
8
+ padding: 1rem;
9
+ padding-right: calc(120px + 3rem); /* Button width + spacing */
10
+ box-sizing: border-box;
11
+ background-color: #ffffff;
12
+ z-index: 1;
13
+ margin-bottom: 1rem;
14
+ width: 100%;
15
+ max-width: 100%;
16
+ overflow: visible;
17
+ }
18
+
19
+ .server-item-content:hover {
20
+ border-color: transparent;
21
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
22
+ transform: translateY(-1px);
23
+ }
24
+
25
+ /* Details widget */
26
+ .details-widget {
27
+ max-height: 0;
28
+ overflow: hidden;
29
+ transition: max-height 0.3s ease-out;
30
+ background-color: #f8fafc;
31
+ border-radius: 0 0 0.5rem 0.5rem;
32
+ margin: -1px 0 0;
33
+ border: 1px solid #e5e7eb;
34
+ border-top: none;
35
+ position: relative;
36
+ z-index: 0;
37
+ width: 100%;
38
+ max-width: 100%;
39
+ box-sizing: border-box;
40
+ left: 0;
41
+ right: 0;
42
+ }
43
+
44
+ .details-widget.expanded {
45
+ max-height: 2000px; /* Increased height to accommodate more content */
46
+ border-color: transparent;
47
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
48
+ transition: max-height 0.3s ease-in-out, box-shadow 0.2s ease;
49
+ width: 100%;
50
+ margin-left: 0;
51
+ margin-right: 0;
52
+ display: block;
53
+ }
54
+
55
+ .details-widget-content {
56
+ padding: 1rem;
57
+ width: 100%;
58
+ box-sizing: border-box;
59
+ }
60
+
61
+ .description-text {
62
+ color: #4b5563;
63
+ line-height: 1.5;
64
+ font-size: 0.875rem;
65
+ }
66
+
67
+ /* Expand/collapse animation */
68
+ .server-item-content.expanded {
69
+ border-bottom: none;
70
+ border-bottom-left-radius: 0;
71
+ border-bottom-right-radius: 0;
72
+ border-color: transparent;
73
+ box-shadow: 0 -1px 8px rgba(0, 0, 0, 0.08);
74
+ margin-bottom: 0;
75
+ }
76
+
77
+ /* Server item layout */
78
+ .server-item-info {
79
+ width: 100%;
80
+ }
81
+
82
+ .server-item-header {
83
+ margin-bottom: 1rem;
84
+ }
85
+
86
+ .server-item-header h5 {
87
+ margin-bottom: 0.5rem;
88
+ }
89
+
90
+ /* Client status section */
91
+ .flex-wrap {
92
+ margin: -0.25rem; /* Negative margin to offset badge spacing */
93
+ }
94
+
95
+ .flex-wrap > * {
96
+ margin: 0.25rem; /* Even spacing between badges */
97
+ }
98
+
99
+ /* Install/Uninstall button section */
100
+ .action-buttons {
101
+ position: absolute;
102
+ right: 1rem;
103
+ top: calc(2rem + 0.5rem); /* Align with description text (header height + margin-bottom) */
104
+ margin: 0;
105
+ z-index: 2; /* Ensure buttons stay on top */
106
+ }
107
+
108
+ .action-buttons button {
109
+ min-width: 100px;
110
+ padding: 0.5rem 1.5rem;
111
+ text-align: center;
112
+ font-weight: 600;
113
+ transition: all 0.15s ease;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ /* Status badges */
118
+ .server-item-info .flex-wrap span {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ padding: 0.375rem 0.75rem;
122
+ border-radius: 9999px;
123
+ font-size: 0.75rem;
124
+ line-height: 1;
125
+ white-space: nowrap;
126
+ }
@@ -12,6 +12,8 @@
12
12
  <link rel="stylesheet" href="styles.css">
13
13
  <link rel="stylesheet" href="css/modal.css">
14
14
  <link rel="stylesheet" href="css/notifications.css">
15
+ <link rel="stylesheet" href="css/serverDetails.css">
16
+ <link rel="stylesheet" href="css/detailsWidget.css">
15
17
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
16
18
 
17
19
  <!-- Alert container for notifications -->
@@ -0,0 +1,264 @@
1
+ export class DetailsWidget {
2
+ constructor(container) {
3
+ this.container = container;
4
+ this.isExpanded = false;
5
+ this.expandedTool = null;
6
+ this.init();
7
+ }
8
+
9
+ init() {
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
+ // Create wrapper to maintain proper width alignment
16
+ this.containerWrapper = document.createElement('div');
17
+ this.containerWrapper.className = 'details-widget-container';
18
+ this.containerWrapper.style.width = '100%';
19
+ this.containerWrapper.style.boxSizing = 'border-box';
20
+ this.containerWrapper.style.maxWidth = '100%';
21
+ this.containerWrapper.style.overflow = 'hidden';
22
+
23
+ // Create the widget element
24
+ this.widgetElement = document.createElement('div');
25
+ this.widgetElement.className = 'details-widget';
26
+ this.widgetElement.style.width = '100%';
27
+ this.widgetElement.style.boxSizing = 'border-box';
28
+ this.widgetElement.style.maxWidth = '100%';
29
+
30
+ // Create the content element
31
+ this.contentElement = document.createElement('div');
32
+ this.contentElement.className = 'details-widget-content';
33
+ this.contentElement.style.width = '100%';
34
+ this.contentElement.style.boxSizing = 'border-box';
35
+
36
+ // Build the DOM structure
37
+ this.widgetElement.appendChild(this.contentElement);
38
+ this.containerWrapper.appendChild(this.widgetElement);
39
+
40
+ // Insert after the server item while maintaining proper structure
41
+ this.container.after(this.containerWrapper);
42
+ }
43
+
44
+ setContent(content) {
45
+ if (typeof content === 'string') {
46
+ this.contentElement.innerHTML = `
47
+ <div class="description-text">
48
+ ${content || 'No description available.'}
49
+ </div>
50
+ `;
51
+ return;
52
+ }
53
+
54
+ // Handle direct schema object or wrapped schema object
55
+ if (typeof content === 'object') {
56
+ const schemaData = content.schema || content; // Handle both {schema: data} and direct data
57
+ this.renderToolsList(schemaData);
58
+ return;
59
+ }
60
+
61
+ this.contentElement.innerHTML = '<p>Invalid content format</p>';
62
+ }
63
+
64
+ // Maintain compatibility with old toggle behavior
65
+ toggle() {
66
+ if (this.isExpanded) {
67
+ this.collapse();
68
+ } else {
69
+ this.expand();
70
+ }
71
+ }
72
+
73
+ expand() {
74
+ this.isExpanded = true;
75
+ this.widgetElement.classList.add('expanded');
76
+
77
+ // Ensure proper dimensions based on container
78
+ const containerWidth = this.container.offsetWidth;
79
+ this.containerWrapper.style.width = `${containerWidth}px`;
80
+ this.containerWrapper.style.maxWidth = '100%';
81
+ this.containerWrapper.style.display = 'block';
82
+
83
+ // Add expanded state to container
84
+ if (this.container) {
85
+ this.container.classList.add('expanded');
86
+ }
87
+ }
88
+
89
+ collapse() {
90
+ this.isExpanded = false;
91
+ this.widgetElement.classList.remove('expanded');
92
+ if (this.container) {
93
+ this.container.classList.remove('expanded');
94
+ }
95
+ // Reset expanded tool state
96
+ this.expandedTool = null;
97
+ }
98
+
99
+ renderToolsList(schemaContent) {
100
+ if (!schemaContent || typeof schemaContent !== 'object') {
101
+ this.contentElement.innerHTML = '<p>Invalid schema format</p>';
102
+ return;
103
+ }
104
+
105
+ let html = `
106
+ <div class="tools-list">
107
+ `;
108
+
109
+ Object.entries(schemaContent).forEach(([toolName, toolInfo]) => {
110
+ if (!toolInfo) return;
111
+
112
+ html += `<div class="tool-card" data-tool="${this.escapeHtml(toolName)}">
113
+ <div class="tool-card-header">
114
+ <div class="tool-header">
115
+ <h3 class="text-md font-semibold text-blue-600">${toolInfo.name || toolName}</h3>
116
+ <div class="text-gray-600 text-sm">${toolInfo.description || 'No description available'}</div>
117
+ </div>
118
+ </div>
119
+ <div class="tool-details hidden" data-tool-details="${this.escapeHtml(toolName)}">
120
+ ${this.renderInputSchema(toolInfo.inputSchema)}
121
+ </div>
122
+ </div>
123
+ `;
124
+ });
125
+
126
+ html += `
127
+ </div>
128
+ `;
129
+
130
+ this.contentElement.innerHTML = html;
131
+
132
+ // Add event listeners to tool cards
133
+ this.contentElement.querySelectorAll('.tool-card').forEach(card => {
134
+ const header = card.querySelector('.tool-card-header');
135
+ header.addEventListener('click', (e) => {
136
+ e.stopPropagation(); // Prevent click from bubbling up to server-item
137
+ const toolName = card.dataset.tool;
138
+ this.toggleToolDetails(toolName, card);
139
+ });
140
+
141
+ // Add click handler for the details area to prevent bubbling
142
+ const details = card.querySelector('.tool-details');
143
+ details.addEventListener('click', (e) => {
144
+ e.stopPropagation(); // Prevent click from bubbling up
145
+ });
146
+ });
147
+ }
148
+
149
+ toggleToolDetails(toolName, card) {
150
+ const detailsElement = card.querySelector(`[data-tool-details="${toolName}"]`);
151
+ const isExpanded = !detailsElement.classList.contains('hidden');
152
+
153
+ // Collapse all other expanded tools
154
+ if (this.expandedTool && this.expandedTool !== toolName) {
155
+ const prevCard = this.contentElement.querySelector(`[data-tool="${this.expandedTool}"]`);
156
+ if (prevCard) {
157
+ const prevDetails = prevCard.querySelector(`[data-tool-details="${this.expandedTool}"]`);
158
+ prevDetails?.classList.add('hidden');
159
+ prevCard.classList.remove('active');
160
+ setTimeout(() => {
161
+ prevDetails.classList.remove('visible');
162
+ }, 0);
163
+ }
164
+ }
165
+
166
+ // Toggle current tool with animation
167
+ if (isExpanded) {
168
+ detailsElement.classList.remove('visible');
169
+ card.classList.remove('active');
170
+ setTimeout(() => {
171
+ detailsElement.classList.add('hidden');
172
+ }, 300); // Match transition duration from CSS
173
+ this.expandedTool = null;
174
+ } else {
175
+ detailsElement.classList.remove('hidden');
176
+ card.classList.add('active');
177
+ setTimeout(() => {
178
+ detailsElement.classList.add('visible');
179
+ }, 0);
180
+ this.expandedTool = toolName;
181
+ detailsElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
182
+ }
183
+ }
184
+
185
+ renderInputSchema(schema) {
186
+ if (!schema || typeof schema !== 'object') {
187
+ return '<p>No input schema available</p>';
188
+ }
189
+
190
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
191
+ return '<p>No input properties defined</p>';
192
+ }
193
+
194
+ let html = '<div class="properties-list">';
195
+
196
+ try {
197
+ // Render each property
198
+ Object.entries(schema.properties).forEach(([propName, propDetails]) => {
199
+ if (!propDetails) return;
200
+
201
+ const isRequired = schema.required?.includes(propName);
202
+ const type = this.getPropertyType(propDetails);
203
+
204
+ html += `<div class="property-item ${isRequired ? 'required' : ''}">
205
+ <div class="property-header">
206
+ <span class="property-name">${this.escapeHtml(propName)}${isRequired ? '<span class="required-star">*</span>' : ''}</span><span class="property-type">${this.escapeHtml(type)}</span>${propDetails.description ? `<span class="property-desc">${this.escapeHtml(propDetails.description)}</span>` : ''}
207
+ ${propDetails.default !== undefined ? `<div class="property-default">Default: <code>${this.escapeHtml(JSON.stringify(propDetails.default))}</code></div>` : ''}
208
+ </div>
209
+ ${this.renderNestedProperties(propDetails)}
210
+ </div>
211
+ `;
212
+ });
213
+
214
+ return html + '</div>';
215
+ } catch (error) {
216
+ console.error('Error rendering input schema:', error);
217
+ return '<p>Error rendering input schema</p>';
218
+ }
219
+ }
220
+
221
+ renderNestedProperties(propDetails) {
222
+ if (propDetails.properties) {
223
+ let html = '<div class="nested-properties">';
224
+ Object.entries(propDetails.properties).forEach(([name, details]) => {
225
+ const type = this.getPropertyType(details);
226
+ html += `
227
+ <div class="nested-property-item">
228
+ <span class="property-name">${this.escapeHtml(name)}</span><span class="property-type">${this.escapeHtml(type)}</span>
229
+ ${details.description ? `<div class="property-desc">${this.escapeHtml(details.description)}</div>` : ''}
230
+ </div>
231
+ `;
232
+ });
233
+ return html + '</div>';
234
+ }
235
+ return '';
236
+ }
237
+
238
+ getPropertyType(propDetails) {
239
+ if (propDetails.anyOf) {
240
+ return `oneOf: [${propDetails.anyOf.map(type => type.type || 'any').join(', ')}]`;
241
+ }
242
+ if (propDetails.type === 'array') {
243
+ const itemType = propDetails.items?.type || 'any';
244
+ return `array<${itemType}>`;
245
+ }
246
+ return propDetails.type || 'any';
247
+ }
248
+
249
+ escapeHtml(unsafe) {
250
+ if (unsafe === undefined || unsafe === null) {
251
+ return '';
252
+ }
253
+ return String(unsafe)
254
+ .replace(/&/g, "&amp;")
255
+ .replace(/</g, "&lt;")
256
+ .replace(/>/g, "&gt;")
257
+ .replace(/"/g, "&quot;")
258
+ .replace(/'/g, "&#039;");
259
+ }
260
+
261
+ isVisible() {
262
+ return this.isExpanded;
263
+ }
264
+ }