imcp 0.0.7 → 0.0.9
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 +5 -0
- package/dist/core/ConfigurationLoader.js +6 -4
- package/dist/core/installers/clients/ClientInstaller.js +39 -25
- package/dist/web/public/css/serverDetails.css +98 -0
- package/dist/web/public/js/detailsWidget.js +48 -0
- package/package.json +1 -1
- package/src/core/ConfigurationLoader.ts +16 -14
- package/src/core/installers/clients/ClientInstaller.ts +40 -22
package/README.md
CHANGED
|
@@ -11,7 +11,12 @@ IMCP allows you to:
|
|
|
11
11
|
- (in progress) Distribute your own MCP servers to others
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
|
+
- Quick usage with latest version
|
|
15
|
+
```
|
|
16
|
+
npx -y imcp@latest serve
|
|
17
|
+
```
|
|
14
18
|
|
|
19
|
+
- Or install it globally
|
|
15
20
|
```bash
|
|
16
21
|
npm install -g imcp
|
|
17
22
|
```
|
|
@@ -105,10 +105,11 @@ export class ConfigurationLoader {
|
|
|
105
105
|
* Synchronizes server categories with feeds
|
|
106
106
|
*/
|
|
107
107
|
static async syncServerCategoriesWithFeeds(configuration) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
// Filter out categories that don't have corresponding feeds and update existing ones
|
|
109
|
+
configuration.localServerCategories = configuration.localServerCategories
|
|
110
|
+
.filter(server => configuration.feeds[server.name])
|
|
111
|
+
.map(server => {
|
|
112
|
+
server.feedConfiguration = configuration.feeds[server.name];
|
|
112
113
|
if (!server.installationStatus ||
|
|
113
114
|
!server.installationStatus.requirementsStatus ||
|
|
114
115
|
Object.keys(server.installationStatus.requirementsStatus).length === 0 ||
|
|
@@ -118,6 +119,7 @@ export class ConfigurationLoader {
|
|
|
118
119
|
}
|
|
119
120
|
return server;
|
|
120
121
|
});
|
|
122
|
+
// Add new categories for feeds that don't have a corresponding category
|
|
121
123
|
const existingServerNames = new Set(configuration.localServerCategories.map(category => category.name));
|
|
122
124
|
for (const [feedName, feedConfig] of Object.entries(configuration.feeds)) {
|
|
123
125
|
if (!existingServerNames.has(feedName)) {
|
|
@@ -45,38 +45,52 @@ export class ClientInstaller {
|
|
|
45
45
|
*/
|
|
46
46
|
async isCommandAvailable(command) {
|
|
47
47
|
try {
|
|
48
|
-
|
|
49
|
-
// Windows-specific command check
|
|
50
|
-
await execAsync(`where ${command}`);
|
|
51
|
-
}
|
|
52
|
-
else if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
|
|
53
|
-
// macOS-specific VS Code check
|
|
54
|
-
const vscodePath = command === 'code' ?
|
|
55
|
-
'/Applications/Visual Studio Code.app' :
|
|
56
|
-
'/Applications/Visual Studio Code - Insiders.app';
|
|
57
|
-
await execAsync(`test -d "${vscodePath}"`);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
// Unix-like systems
|
|
61
|
-
await execAsync(`which ${command}`);
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
48
|
+
// For VS Code on macOS, check both command-line tool and app bundle
|
|
66
49
|
if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
|
|
67
|
-
// Try checking in ~/Applications as well for macOS
|
|
68
50
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`${homedir}/Applications/Visual Studio Code.app` :
|
|
72
|
-
`${homedir}/Applications/Visual Studio Code - Insiders.app`;
|
|
73
|
-
await execAsync(`test -d "${vscodePath}"`);
|
|
51
|
+
// Try which command first
|
|
52
|
+
await execAsync(`which ${command}`);
|
|
74
53
|
return true;
|
|
75
54
|
}
|
|
76
55
|
catch (error) {
|
|
77
|
-
|
|
56
|
+
// If which fails, check application bundles
|
|
57
|
+
const systemVSCodePath = command === 'code' ?
|
|
58
|
+
'/Applications/Visual Studio Code.app' :
|
|
59
|
+
'/Applications/Visual Studio Code - Insiders.app';
|
|
60
|
+
try {
|
|
61
|
+
// Check system Applications first
|
|
62
|
+
await execAsync(`test -d "${systemVSCodePath}"`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// If system Applications check fails, try user Applications
|
|
67
|
+
const homedir = process.env.HOME;
|
|
68
|
+
if (homedir) {
|
|
69
|
+
const userVSCodePath = command === 'code' ?
|
|
70
|
+
`${homedir}/Applications/Visual Studio Code.app` :
|
|
71
|
+
`${homedir}/Applications/Visual Studio Code - Insiders.app`;
|
|
72
|
+
try {
|
|
73
|
+
await execAsync(`test -d "${userVSCodePath}"`);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
}
|
|
84
|
+
// For Windows, use where command
|
|
85
|
+
if (process.platform === 'win32') {
|
|
86
|
+
await execAsync(`where ${command}`);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// For all other cases (Unix-like systems), use which command
|
|
90
|
+
await execAsync(`which ${command}`);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
80
94
|
return false;
|
|
81
95
|
}
|
|
82
96
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Server item container */
|
|
2
|
+
.server-item {
|
|
3
|
+
cursor: pointer;
|
|
4
|
+
position: relative;
|
|
5
|
+
transition: all 0.2s ease;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.server-item-content {
|
|
9
|
+
border: 1px solid #e5e7eb;
|
|
10
|
+
border-radius: 0.5rem;
|
|
11
|
+
padding: 1rem;
|
|
12
|
+
margin-bottom: 1rem;
|
|
13
|
+
background-color: #ffffff;
|
|
14
|
+
transition: all 0.2s ease;
|
|
15
|
+
position: relative;
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
align-items: bottom; /* Center items vertically */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.server-item:hover .server-item-content {
|
|
22
|
+
border-color: #3b82f6;
|
|
23
|
+
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
|
|
24
|
+
transform: translateY(-1px);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Details widget */
|
|
28
|
+
.details-widget {
|
|
29
|
+
max-height: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
transition: max-height 0.3s ease-out;
|
|
32
|
+
background-color: #f8fafc;
|
|
33
|
+
border-radius: 0 0 0.5rem 0.5rem;
|
|
34
|
+
margin-top: -1rem;
|
|
35
|
+
margin-bottom: 1rem;
|
|
36
|
+
border: 1px solid #e5e7eb;
|
|
37
|
+
border-top: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.details-widget.expanded {
|
|
41
|
+
max-height: 500px; /* Adjust based on content */
|
|
42
|
+
border-color: #3b82f6;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.details-widget-content {
|
|
46
|
+
padding: 1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.description-text {
|
|
50
|
+
color: #4b5563;
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
font-size: 0.875rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Expand/collapse animation */
|
|
56
|
+
.server-item-content.expanded {
|
|
57
|
+
border-bottom-left-radius: 0;
|
|
58
|
+
border-bottom-right-radius: 0;
|
|
59
|
+
border-color: #3b82f6;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Server item content layout */
|
|
63
|
+
.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 */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Ensure buttons stay in place on hover */
|
|
78
|
+
.server-item:hover .action-buttons {
|
|
79
|
+
position: relative;
|
|
80
|
+
z-index: 2;
|
|
81
|
+
}
|
|
82
|
+
|
|
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;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Status badges layout */
|
|
92
|
+
.server-item-info .flex-wrap {
|
|
93
|
+
margin: -0.25rem; /* Negative margin to offset badge spacing */
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.server-item-info .flex-wrap > * {
|
|
97
|
+
margin: 0.25rem; /* Even spacing between badges */
|
|
98
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class DetailsWidget {
|
|
2
|
+
constructor(container) {
|
|
3
|
+
this.container = container;
|
|
4
|
+
this.isExpanded = false;
|
|
5
|
+
this.init();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
init() {
|
|
9
|
+
// Create details widget elements
|
|
10
|
+
this.widgetElement = document.createElement('div');
|
|
11
|
+
this.widgetElement.className = 'details-widget';
|
|
12
|
+
this.contentElement = document.createElement('div');
|
|
13
|
+
this.contentElement.className = 'details-widget-content';
|
|
14
|
+
this.widgetElement.appendChild(this.contentElement);
|
|
15
|
+
this.container.appendChild(this.widgetElement);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setContent(description) {
|
|
19
|
+
this.contentElement.innerHTML = `
|
|
20
|
+
<div class="description-text">
|
|
21
|
+
${description || 'No description available.'}
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toggle() {
|
|
27
|
+
this.isExpanded = !this.isExpanded;
|
|
28
|
+
if (this.isExpanded) {
|
|
29
|
+
this.expand();
|
|
30
|
+
} else {
|
|
31
|
+
this.collapse();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
expand() {
|
|
36
|
+
this.widgetElement.classList.add('expanded');
|
|
37
|
+
this.container.querySelector('.server-item-content').classList.add('expanded');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
collapse() {
|
|
41
|
+
this.widgetElement.classList.remove('expanded');
|
|
42
|
+
this.container.querySelector('.server-item-content').classList.remove('expanded');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isVisible() {
|
|
46
|
+
return this.isExpanded;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -133,24 +133,26 @@ export class ConfigurationLoader {
|
|
|
133
133
|
* Synchronizes server categories with feeds
|
|
134
134
|
*/
|
|
135
135
|
private static async syncServerCategoriesWithFeeds(configuration: MCPConfiguration): Promise<MCPConfiguration> {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
// Filter out categories that don't have corresponding feeds and update existing ones
|
|
137
|
+
configuration.localServerCategories = configuration.localServerCategories
|
|
138
|
+
.filter(server => configuration.feeds[server.name])
|
|
139
|
+
.map(server => {
|
|
138
140
|
server.feedConfiguration = configuration.feeds[server.name];
|
|
139
|
-
}
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
if (
|
|
143
|
+
!server.installationStatus ||
|
|
144
|
+
!server.installationStatus.requirementsStatus ||
|
|
145
|
+
Object.keys(server.installationStatus.requirementsStatus).length === 0 ||
|
|
146
|
+
!server.installationStatus.serversStatus ||
|
|
147
|
+
Object.keys(server.installationStatus.serversStatus).length < Object.keys(server.feedConfiguration?.mcpServers || []).length
|
|
148
|
+
) {
|
|
149
|
+
server.installationStatus = ConfigurationLoader.initializeInstallationStatus(server.feedConfiguration);
|
|
150
|
+
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
return server;
|
|
153
|
+
});
|
|
153
154
|
|
|
155
|
+
// Add new categories for feeds that don't have a corresponding category
|
|
154
156
|
const existingServerNames = new Set(configuration.localServerCategories.map(category => category.name));
|
|
155
157
|
|
|
156
158
|
for (const [feedName, feedConfig] of Object.entries(configuration.feeds)) {
|
|
@@ -60,34 +60,52 @@ export class ClientInstaller {
|
|
|
60
60
|
*/
|
|
61
61
|
private async isCommandAvailable(command: string): Promise<boolean> {
|
|
62
62
|
try {
|
|
63
|
-
|
|
64
|
-
// Windows-specific command check
|
|
65
|
-
await execAsync(`where ${command}`);
|
|
66
|
-
} else if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
|
|
67
|
-
// macOS-specific VS Code check
|
|
68
|
-
const vscodePath = command === 'code' ?
|
|
69
|
-
'/Applications/Visual Studio Code.app' :
|
|
70
|
-
'/Applications/Visual Studio Code - Insiders.app';
|
|
71
|
-
await execAsync(`test -d "${vscodePath}"`);
|
|
72
|
-
} else {
|
|
73
|
-
// Unix-like systems
|
|
74
|
-
await execAsync(`which ${command}`);
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
77
|
-
} catch (error) {
|
|
63
|
+
// For VS Code on macOS, check both command-line tool and app bundle
|
|
78
64
|
if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
|
|
79
|
-
// Try checking in ~/Applications as well for macOS
|
|
80
65
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`${homedir}/Applications/Visual Studio Code.app` :
|
|
84
|
-
`${homedir}/Applications/Visual Studio Code - Insiders.app`;
|
|
85
|
-
await execAsync(`test -d "${vscodePath}"`);
|
|
66
|
+
// Try which command first
|
|
67
|
+
await execAsync(`which ${command}`);
|
|
86
68
|
return true;
|
|
87
69
|
} catch (error) {
|
|
88
|
-
|
|
70
|
+
// If which fails, check application bundles
|
|
71
|
+
const systemVSCodePath = command === 'code' ?
|
|
72
|
+
'/Applications/Visual Studio Code.app' :
|
|
73
|
+
'/Applications/Visual Studio Code - Insiders.app';
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Check system Applications first
|
|
77
|
+
await execAsync(`test -d "${systemVSCodePath}"`);
|
|
78
|
+
return true;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// If system Applications check fails, try user Applications
|
|
81
|
+
const homedir = process.env.HOME;
|
|
82
|
+
if (homedir) {
|
|
83
|
+
const userVSCodePath = command === 'code' ?
|
|
84
|
+
`${homedir}/Applications/Visual Studio Code.app` :
|
|
85
|
+
`${homedir}/Applications/Visual Studio Code - Insiders.app`;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await execAsync(`test -d "${userVSCodePath}"`);
|
|
89
|
+
return true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
89
96
|
}
|
|
90
97
|
}
|
|
98
|
+
|
|
99
|
+
// For Windows, use where command
|
|
100
|
+
if (process.platform === 'win32') {
|
|
101
|
+
await execAsync(`where ${command}`);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// For all other cases (Unix-like systems), use which command
|
|
106
|
+
await execAsync(`which ${command}`);
|
|
107
|
+
return true;
|
|
108
|
+
} catch (error) {
|
|
91
109
|
return false;
|
|
92
110
|
}
|
|
93
111
|
}
|