@maplezzk/mcps 1.0.29 → 1.0.30-beta.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 +1 -0
- package/dist/core/config.js +13 -8
- package/dist/index.js +0 -0
- package/dist/tests/helpers.js +52 -0
- package/dist/tests/unit/config.test.js +218 -0
- package/package.json +14 -4
- package/dist/commands/config.js +0 -83
package/README.md
CHANGED
package/dist/core/config.js
CHANGED
|
@@ -2,24 +2,29 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { ServerConfigSchema } from '../types/config.js';
|
|
5
|
-
const
|
|
6
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'mcp.json');
|
|
5
|
+
const getDefaultConfigDir = () => process.env.MCPS_CONFIG_DIR || path.join(os.homedir(), '.mcps');
|
|
7
6
|
export class ConfigManager {
|
|
7
|
+
configDir;
|
|
8
|
+
configFile;
|
|
9
|
+
constructor(configDir) {
|
|
10
|
+
this.configDir = configDir || getDefaultConfigDir();
|
|
11
|
+
this.configFile = path.join(this.configDir, 'mcp.json');
|
|
12
|
+
}
|
|
8
13
|
ensureConfigDir() {
|
|
9
|
-
if (!fs.existsSync(
|
|
10
|
-
fs.mkdirSync(
|
|
14
|
+
if (!fs.existsSync(this.configDir)) {
|
|
15
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
11
16
|
}
|
|
12
17
|
}
|
|
13
18
|
loadConfig() {
|
|
14
19
|
this.ensureConfigDir();
|
|
15
|
-
if (!fs.existsSync(
|
|
20
|
+
if (!fs.existsSync(this.configFile)) {
|
|
16
21
|
return { servers: [] };
|
|
17
22
|
}
|
|
18
23
|
try {
|
|
19
|
-
const content = fs.readFileSync(
|
|
24
|
+
const content = fs.readFileSync(this.configFile, 'utf-8');
|
|
20
25
|
const json = JSON.parse(content);
|
|
21
26
|
// Log for debugging (can be removed later or controlled by verbose flag)
|
|
22
|
-
// console.log('Loading config from:',
|
|
27
|
+
// console.log('Loading config from:', this.configFile);
|
|
23
28
|
if (!json || typeof json !== 'object') {
|
|
24
29
|
console.warn('Invalid config file structure. Expected JSON object.');
|
|
25
30
|
return { servers: [] };
|
|
@@ -59,7 +64,7 @@ export class ConfigManager {
|
|
|
59
64
|
}
|
|
60
65
|
saveConfig(config) {
|
|
61
66
|
this.ensureConfigDir();
|
|
62
|
-
fs.writeFileSync(
|
|
67
|
+
fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2), 'utf-8');
|
|
63
68
|
}
|
|
64
69
|
listServers() {
|
|
65
70
|
return this.loadConfig().servers;
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
export const TEST_CONFIG_DIR = path.join(os.tmpdir(), `mcps-test-${Date.now()}`);
|
|
6
|
+
export function setupTestConfig() {
|
|
7
|
+
// 确保测试配置目录存在
|
|
8
|
+
if (!fs.existsSync(TEST_CONFIG_DIR)) {
|
|
9
|
+
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
// 设置测试环境变量
|
|
12
|
+
process.env.MCPS_CONFIG_DIR = TEST_CONFIG_DIR;
|
|
13
|
+
return TEST_CONFIG_DIR;
|
|
14
|
+
}
|
|
15
|
+
export function cleanupTestConfig() {
|
|
16
|
+
if (fs.existsSync(TEST_CONFIG_DIR)) {
|
|
17
|
+
fs.rmSync(TEST_CONFIG_DIR, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function getTestConfigPath() {
|
|
21
|
+
return path.join(TEST_CONFIG_DIR, 'mcp.json');
|
|
22
|
+
}
|
|
23
|
+
export function createTestServer(overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
name: 'test-server',
|
|
26
|
+
type: 'stdio',
|
|
27
|
+
command: 'node',
|
|
28
|
+
args: ['--version'],
|
|
29
|
+
...overrides
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createMockClient(toolsCount = 5) {
|
|
33
|
+
const tools = [];
|
|
34
|
+
for (let i = 0; i < toolsCount; i++) {
|
|
35
|
+
tools.push({
|
|
36
|
+
name: `test_tool_${i}`,
|
|
37
|
+
description: `Test tool ${i}`,
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
param: { type: 'string', description: `Parameter ${i}` }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
listTools: vi.fn().mockResolvedValue({ tools }),
|
|
48
|
+
callTool: vi.fn().mockResolvedValue({ result: 'success' }),
|
|
49
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
50
|
+
close: vi.fn().mockResolvedValue(undefined)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { ConfigManager } from '../../core/config.js';
|
|
6
|
+
describe('ConfigManager', () => {
|
|
7
|
+
let testConfigDir;
|
|
8
|
+
let manager;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// 创建临时配置目录
|
|
11
|
+
testConfigDir = path.join(os.tmpdir(), `mcps-config-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
|
12
|
+
fs.mkdirSync(testConfigDir, { recursive: true });
|
|
13
|
+
// 创建新的 ConfigManager 实例,传入测试目录
|
|
14
|
+
manager = new ConfigManager(testConfigDir);
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// 清理临时目录
|
|
18
|
+
if (fs.existsSync(testConfigDir)) {
|
|
19
|
+
try {
|
|
20
|
+
fs.rmSync(testConfigDir, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
// 忽略清理错误
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
describe('addServer', () => {
|
|
28
|
+
it('should add a stdio server', () => {
|
|
29
|
+
const server = {
|
|
30
|
+
name: 'test-stdio',
|
|
31
|
+
type: 'stdio',
|
|
32
|
+
command: 'node',
|
|
33
|
+
args: ['--version']
|
|
34
|
+
};
|
|
35
|
+
manager.addServer(server);
|
|
36
|
+
const servers = manager.listServers();
|
|
37
|
+
expect(servers).toHaveLength(1);
|
|
38
|
+
expect(servers[0]).toMatchObject(server);
|
|
39
|
+
});
|
|
40
|
+
it('should add an sse server', () => {
|
|
41
|
+
const server = {
|
|
42
|
+
name: 'test-sse',
|
|
43
|
+
type: 'sse',
|
|
44
|
+
url: 'http://localhost:3000/sse'
|
|
45
|
+
};
|
|
46
|
+
manager.addServer(server);
|
|
47
|
+
const retrieved = manager.getServer('test-sse');
|
|
48
|
+
expect(retrieved).toMatchObject(server);
|
|
49
|
+
});
|
|
50
|
+
it('should add an http server', () => {
|
|
51
|
+
const server = {
|
|
52
|
+
name: 'test-http',
|
|
53
|
+
type: 'http',
|
|
54
|
+
url: 'http://localhost:3000/mcp'
|
|
55
|
+
};
|
|
56
|
+
manager.addServer(server);
|
|
57
|
+
const retrieved = manager.getServer('test-http');
|
|
58
|
+
expect(retrieved).toMatchObject(server);
|
|
59
|
+
});
|
|
60
|
+
it('should add server with disabled flag', () => {
|
|
61
|
+
const server = {
|
|
62
|
+
name: 'disabled-server',
|
|
63
|
+
type: 'stdio',
|
|
64
|
+
command: 'node',
|
|
65
|
+
args: [],
|
|
66
|
+
disabled: true
|
|
67
|
+
};
|
|
68
|
+
manager.addServer(server);
|
|
69
|
+
const retrieved = manager.getServer('disabled-server');
|
|
70
|
+
expect(retrieved).toMatchObject({
|
|
71
|
+
name: 'disabled-server',
|
|
72
|
+
disabled: true
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it('should throw error when adding duplicate server', () => {
|
|
76
|
+
const server = {
|
|
77
|
+
name: 'duplicate',
|
|
78
|
+
type: 'stdio',
|
|
79
|
+
command: 'node',
|
|
80
|
+
args: []
|
|
81
|
+
};
|
|
82
|
+
manager.addServer(server);
|
|
83
|
+
expect(() => manager.addServer(server)).toThrow('already exists');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('getServer', () => {
|
|
87
|
+
it('should retrieve existing server', () => {
|
|
88
|
+
const server = {
|
|
89
|
+
name: 'to-retrieve',
|
|
90
|
+
type: 'stdio',
|
|
91
|
+
command: 'node',
|
|
92
|
+
args: []
|
|
93
|
+
};
|
|
94
|
+
manager.addServer(server);
|
|
95
|
+
const retrieved = manager.getServer('to-retrieve');
|
|
96
|
+
expect(retrieved).toMatchObject(server);
|
|
97
|
+
});
|
|
98
|
+
it('should return undefined for non-existing server', () => {
|
|
99
|
+
const retrieved = manager.getServer('non-existing');
|
|
100
|
+
expect(retrieved).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('listServers', () => {
|
|
104
|
+
it('should return empty array when no servers', () => {
|
|
105
|
+
const servers = manager.listServers();
|
|
106
|
+
expect(servers).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
it('should return all servers including disabled ones', () => {
|
|
109
|
+
const server1 = {
|
|
110
|
+
name: 'server-1',
|
|
111
|
+
type: 'stdio',
|
|
112
|
+
command: 'node',
|
|
113
|
+
args: []
|
|
114
|
+
};
|
|
115
|
+
const server2 = {
|
|
116
|
+
name: 'server-2',
|
|
117
|
+
type: 'stdio',
|
|
118
|
+
command: 'npm',
|
|
119
|
+
args: ['start'],
|
|
120
|
+
disabled: true
|
|
121
|
+
};
|
|
122
|
+
manager.addServer(server1);
|
|
123
|
+
manager.addServer(server2);
|
|
124
|
+
const servers = manager.listServers();
|
|
125
|
+
expect(servers).toHaveLength(2);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('removeServer', () => {
|
|
129
|
+
it('should remove existing server', () => {
|
|
130
|
+
const server = {
|
|
131
|
+
name: 'to-remove',
|
|
132
|
+
type: 'stdio',
|
|
133
|
+
command: 'node',
|
|
134
|
+
args: []
|
|
135
|
+
};
|
|
136
|
+
manager.addServer(server);
|
|
137
|
+
expect(manager.listServers()).toHaveLength(1);
|
|
138
|
+
manager.removeServer('to-remove');
|
|
139
|
+
expect(manager.listServers()).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
it('should throw error when removing non-existing server', () => {
|
|
142
|
+
expect(() => manager.removeServer('non-existing')).toThrow('not found');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('updateServer', () => {
|
|
146
|
+
it('should update server configuration', () => {
|
|
147
|
+
const original = {
|
|
148
|
+
name: 'to-update',
|
|
149
|
+
type: 'stdio',
|
|
150
|
+
command: 'node',
|
|
151
|
+
args: ['--version']
|
|
152
|
+
};
|
|
153
|
+
manager.addServer(original);
|
|
154
|
+
const updates = {
|
|
155
|
+
command: 'npm',
|
|
156
|
+
args: ['start']
|
|
157
|
+
};
|
|
158
|
+
manager.updateServer('to-update', updates);
|
|
159
|
+
const updated = manager.getServer('to-update');
|
|
160
|
+
expect(updated).toMatchObject({
|
|
161
|
+
name: 'to-update',
|
|
162
|
+
command: 'npm',
|
|
163
|
+
args: ['start']
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
it('should preserve disabled status during update', () => {
|
|
167
|
+
const original = {
|
|
168
|
+
name: 'update-disabled',
|
|
169
|
+
type: 'stdio',
|
|
170
|
+
command: 'node',
|
|
171
|
+
args: [],
|
|
172
|
+
disabled: true
|
|
173
|
+
};
|
|
174
|
+
manager.addServer(original);
|
|
175
|
+
manager.updateServer('update-disabled', { command: 'npm' });
|
|
176
|
+
const updated = manager.getServer('update-disabled');
|
|
177
|
+
expect(updated?.disabled).toBe(true);
|
|
178
|
+
expect(updated?.command).toBe('npm');
|
|
179
|
+
});
|
|
180
|
+
it('should throw error when updating non-existing server', () => {
|
|
181
|
+
expect(() => manager.updateServer('non-existing', {})).toThrow('not found');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('persistence', () => {
|
|
185
|
+
it('should persist configuration to file', () => {
|
|
186
|
+
const server = {
|
|
187
|
+
name: 'persistent',
|
|
188
|
+
type: 'stdio',
|
|
189
|
+
command: 'node',
|
|
190
|
+
args: []
|
|
191
|
+
};
|
|
192
|
+
manager.addServer(server);
|
|
193
|
+
const configFile = path.join(testConfigDir, 'mcp.json');
|
|
194
|
+
expect(fs.existsSync(configFile)).toBe(true);
|
|
195
|
+
const content = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
196
|
+
expect(content.servers).toHaveLength(1);
|
|
197
|
+
expect(content.servers[0].name).toBe('persistent');
|
|
198
|
+
});
|
|
199
|
+
it('should load existing configuration on instantiation', () => {
|
|
200
|
+
const configFile = path.join(testConfigDir, 'mcp.json');
|
|
201
|
+
const existingConfig = {
|
|
202
|
+
servers: [
|
|
203
|
+
{
|
|
204
|
+
name: 'existing',
|
|
205
|
+
type: 'stdio',
|
|
206
|
+
command: 'node',
|
|
207
|
+
args: []
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
fs.writeFileSync(configFile, JSON.stringify(existingConfig, null, 2));
|
|
212
|
+
const newManager = new ConfigManager(testConfigDir);
|
|
213
|
+
const servers = newManager.listServers();
|
|
214
|
+
expect(servers).toHaveLength(1);
|
|
215
|
+
expect(servers[0].name).toBe('existing');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maplezzk/mcps",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30-beta.0",
|
|
4
4
|
"description": "A CLI to manage and use MCP servers",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
],
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/a13835614623/mcps"
|
|
20
|
+
},
|
|
17
21
|
"type": "module",
|
|
18
22
|
"bin": {
|
|
19
23
|
"mcps": "./dist/index.js"
|
|
@@ -23,10 +27,13 @@
|
|
|
23
27
|
],
|
|
24
28
|
"scripts": {
|
|
25
29
|
"build": "tsc",
|
|
26
|
-
"prepublishOnly": "npm run build",
|
|
30
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
27
31
|
"start": "node dist/index.js",
|
|
28
32
|
"dev": "ts-node src/index.ts",
|
|
29
|
-
"test": "
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"test:ui": "vitest --ui",
|
|
36
|
+
"test:coverage": "vitest --coverage"
|
|
30
37
|
},
|
|
31
38
|
"dependencies": {
|
|
32
39
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
@@ -39,7 +46,10 @@
|
|
|
39
46
|
"devDependencies": {
|
|
40
47
|
"@types/eventsource": "^1.1.15",
|
|
41
48
|
"@types/node": "^22.10.7",
|
|
49
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
50
|
+
"@vitest/ui": "^4.0.18",
|
|
42
51
|
"ts-node": "^10.9.2",
|
|
43
|
-
"typescript": "^5.7.3"
|
|
52
|
+
"typescript": "^5.7.3",
|
|
53
|
+
"vitest": "^4.0.18"
|
|
44
54
|
}
|
|
45
55
|
}
|
package/dist/commands/config.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { configManager } from '../core/config.js';
|
|
5
|
-
export const registerConfigCommand = (program) => {
|
|
6
|
-
const configCmd = program.command('config')
|
|
7
|
-
.description('Manage configuration');
|
|
8
|
-
configCmd.command('import <file>')
|
|
9
|
-
.description('Import servers from a JSON configuration file (e.g., mcporter.json)')
|
|
10
|
-
.option('-f, --force', 'Overwrite existing servers with the same name', false)
|
|
11
|
-
.action((file, options) => {
|
|
12
|
-
try {
|
|
13
|
-
const absolutePath = path.resolve(file);
|
|
14
|
-
if (!fs.existsSync(absolutePath)) {
|
|
15
|
-
console.error(chalk.red(`File not found: ${absolutePath}`));
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
19
|
-
let json;
|
|
20
|
-
try {
|
|
21
|
-
json = JSON.parse(content);
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
console.error(chalk.red('Invalid JSON file.'));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
let importedCount = 0;
|
|
28
|
-
let skippedCount = 0;
|
|
29
|
-
// Support standard MCP config format (mcpServers object)
|
|
30
|
-
const serversMap = json.mcpServers || {};
|
|
31
|
-
Object.entries(serversMap).forEach(([name, config]) => {
|
|
32
|
-
// Skip disabled servers
|
|
33
|
-
if (config.disabled === true) {
|
|
34
|
-
skippedCount++;
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const serverName = name;
|
|
38
|
-
let newServer;
|
|
39
|
-
if (config.command) {
|
|
40
|
-
newServer = {
|
|
41
|
-
name: serverName,
|
|
42
|
-
type: 'stdio',
|
|
43
|
-
command: config.command,
|
|
44
|
-
args: config.args || [],
|
|
45
|
-
env: config.env,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
else if (config.url) {
|
|
49
|
-
newServer = {
|
|
50
|
-
name: serverName,
|
|
51
|
-
type: 'sse',
|
|
52
|
-
url: config.url
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
console.warn(chalk.yellow(`Skipping invalid server config for "${serverName}": missing command or url`));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const existing = configManager.getServer(serverName);
|
|
60
|
-
if (existing) {
|
|
61
|
-
if (options.force) {
|
|
62
|
-
configManager.updateServer(serverName, newServer);
|
|
63
|
-
console.log(chalk.gray(`Updated existing server: ${serverName}`));
|
|
64
|
-
importedCount++;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
console.log(chalk.yellow(`Skipping existing server: ${serverName} (use --force to overwrite)`));
|
|
68
|
-
skippedCount++;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
configManager.addServer(newServer);
|
|
73
|
-
console.log(chalk.green(`Imported server: ${serverName}`));
|
|
74
|
-
importedCount++;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
console.log(chalk.bold(`\nImport complete. Imported: ${importedCount}, Skipped: ${skippedCount}`));
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error(chalk.red(`Import failed: ${error.message}`));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
};
|