@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 CHANGED
@@ -13,6 +13,7 @@
13
13
  - 📊 **表格输出**:清晰的服务器状态和工具列表展示
14
14
  - 🔍 **工具筛选**:按关键词筛选工具,支持简洁模式
15
15
  - 🚨 **详细日志**:可选的详细日志模式,方便调试
16
+ - ✅ **自动化测试**:完整的测试套件,确保代码质量
16
17
 
17
18
  ## 安装
18
19
 
@@ -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 CONFIG_DIR = process.env.MCPS_CONFIG_DIR || path.join(os.homedir(), '.mcps');
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(CONFIG_DIR)) {
10
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
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(CONFIG_FILE)) {
20
+ if (!fs.existsSync(this.configFile)) {
16
21
  return { servers: [] };
17
22
  }
18
23
  try {
19
- const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
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:', CONFIG_FILE);
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(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
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.29",
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": "echo \"Error: no test specified\" && exit 1"
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
  }
@@ -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
- };