@maplezzk/mcps 1.0.31 → 1.1.1
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/dist/commands/server.js +13 -14
- package/dist/core/client.js +18 -12
- package/dist/core/config.js +32 -40
- package/dist/tests/unit/config.test.js +80 -80
- package/dist/types/config.js +27 -18
- package/package.json +1 -1
package/dist/commands/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { configManager } from '../core/config.js';
|
|
3
3
|
import { DaemonClient } from '../core/daemon-client.js';
|
|
4
|
+
import { detectServerType } from '../types/config.js';
|
|
4
5
|
export const registerServerCommands = (program) => {
|
|
5
6
|
const listServersAction = () => {
|
|
6
7
|
const servers = configManager.listServers();
|
|
@@ -30,19 +31,21 @@ export const registerServerCommands = (program) => {
|
|
|
30
31
|
// Build table rows
|
|
31
32
|
const rows = servers.map(server => {
|
|
32
33
|
const disabled = server.disabled === true;
|
|
33
|
-
const
|
|
34
|
+
const serverType = detectServerType(server);
|
|
35
|
+
const typeColor = serverType === 'stdio' ? chalk.cyan : chalk.yellow;
|
|
34
36
|
const enabledMark = disabled ? chalk.red('✗') : chalk.green('✓');
|
|
35
37
|
// Build command/URL string
|
|
36
38
|
let command = '';
|
|
37
|
-
if (server
|
|
38
|
-
|
|
39
|
+
if ('command' in server && server.command) {
|
|
40
|
+
const args = server.args;
|
|
41
|
+
command = `${server.command} ${args?.join(' ') || ''}`;
|
|
39
42
|
}
|
|
40
|
-
else {
|
|
41
|
-
command = server.url
|
|
43
|
+
else if ('url' in server && server.url) {
|
|
44
|
+
command = server.url;
|
|
42
45
|
}
|
|
43
46
|
return {
|
|
44
47
|
name: server.name,
|
|
45
|
-
type: typeColor(
|
|
48
|
+
type: typeColor(serverType),
|
|
46
49
|
enabled: enabledMark,
|
|
47
50
|
command: command,
|
|
48
51
|
disabled
|
|
@@ -67,12 +70,10 @@ export const registerServerCommands = (program) => {
|
|
|
67
70
|
};
|
|
68
71
|
const addServerAction = (name, options) => {
|
|
69
72
|
try {
|
|
70
|
-
if (options.type === 'sse' || options.type === 'http') {
|
|
73
|
+
if (options.type === 'sse' || options.type === 'http' || options.url) {
|
|
71
74
|
if (!options.url)
|
|
72
|
-
throw new Error(`URL is required for ${options.type} servers`);
|
|
73
|
-
configManager.addServer({
|
|
74
|
-
name,
|
|
75
|
-
type: options.type,
|
|
75
|
+
throw new Error(`URL is required for ${options.type || 'HTTP/SSE'} servers`);
|
|
76
|
+
configManager.addServer(name, {
|
|
76
77
|
url: options.url,
|
|
77
78
|
});
|
|
78
79
|
}
|
|
@@ -89,9 +90,7 @@ export const registerServerCommands = (program) => {
|
|
|
89
90
|
env[k] = v;
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
|
-
configManager.addServer({
|
|
93
|
-
name,
|
|
94
|
-
type: 'stdio',
|
|
93
|
+
configManager.addServer(name, {
|
|
95
94
|
command: options.command,
|
|
96
95
|
args: options.args || [],
|
|
97
96
|
env: Object.keys(env).length > 0 ? env : undefined,
|
package/dist/core/client.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
3
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
4
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
5
|
+
import { detectServerType } from '../types/config.js';
|
|
5
6
|
import { EventSource } from 'eventsource';
|
|
6
7
|
import { exec } from 'child_process';
|
|
7
8
|
import { promisify } from 'util';
|
|
@@ -59,23 +60,25 @@ export class McpClientService {
|
|
|
59
60
|
static globalPidsBeforeConnection = new Set();
|
|
60
61
|
async connect(config, serverName = '') {
|
|
61
62
|
this.serverName = serverName;
|
|
62
|
-
|
|
63
|
+
const serverType = detectServerType(config);
|
|
64
|
+
this.serverType = serverType;
|
|
63
65
|
this.daemonPid = process.pid;
|
|
64
66
|
try {
|
|
65
|
-
if (
|
|
67
|
+
if (serverType === 'stdio' && 'command' in config) {
|
|
68
|
+
const stdioConfig = config;
|
|
66
69
|
// 保存命令和参数用于后续清理进程
|
|
67
|
-
this.serverCommand =
|
|
68
|
-
this.serverArgs =
|
|
70
|
+
this.serverCommand = stdioConfig.command;
|
|
71
|
+
this.serverArgs = stdioConfig.args || [];
|
|
69
72
|
const resolvedConfigEnv = {};
|
|
70
|
-
if (
|
|
71
|
-
for (const key in
|
|
72
|
-
const val =
|
|
73
|
+
if (stdioConfig.env) {
|
|
74
|
+
for (const key in stdioConfig.env) {
|
|
75
|
+
const val = stdioConfig.env[key];
|
|
73
76
|
if (typeof val === 'string') {
|
|
74
77
|
resolvedConfigEnv[key] = resolveEnvPlaceholders(val);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
|
-
const rawEnv =
|
|
81
|
+
const rawEnv = stdioConfig.env ? { ...process.env, ...resolvedConfigEnv } : process.env;
|
|
79
82
|
const env = {};
|
|
80
83
|
for (const key in rawEnv) {
|
|
81
84
|
const val = rawEnv[key];
|
|
@@ -83,21 +86,24 @@ export class McpClientService {
|
|
|
83
86
|
env[key] = val;
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
|
-
const args =
|
|
89
|
+
const args = stdioConfig.args ? stdioConfig.args.map(arg => resolveEnvPlaceholders(arg)) : [];
|
|
87
90
|
this.transport = new StdioClientTransport({
|
|
88
|
-
command:
|
|
91
|
+
command: stdioConfig.command,
|
|
89
92
|
args,
|
|
90
93
|
env: env,
|
|
91
94
|
});
|
|
92
95
|
}
|
|
93
|
-
else if (
|
|
96
|
+
else if (serverType === 'http' && 'url' in config) {
|
|
94
97
|
const url = resolveEnvPlaceholders(config.url);
|
|
95
98
|
this.transport = new StreamableHTTPClientTransport(new URL(url));
|
|
96
99
|
}
|
|
97
|
-
else {
|
|
100
|
+
else if ('url' in config) {
|
|
98
101
|
const url = resolveEnvPlaceholders(config.url);
|
|
99
102
|
this.transport = new SSEClientTransport(new URL(url));
|
|
100
103
|
}
|
|
104
|
+
else {
|
|
105
|
+
throw new Error('Invalid server configuration: must have either command (for stdio) or url (for sse/http)');
|
|
106
|
+
}
|
|
101
107
|
this.client = new Client({
|
|
102
108
|
name: 'mcp-cli',
|
|
103
109
|
version: '1.0.0',
|
package/dist/core/config.js
CHANGED
|
@@ -18,48 +18,36 @@ export class ConfigManager {
|
|
|
18
18
|
loadConfig() {
|
|
19
19
|
this.ensureConfigDir();
|
|
20
20
|
if (!fs.existsSync(this.configFile)) {
|
|
21
|
-
return {
|
|
21
|
+
return { mcpServers: {} };
|
|
22
22
|
}
|
|
23
23
|
try {
|
|
24
24
|
const content = fs.readFileSync(this.configFile, 'utf-8');
|
|
25
25
|
const json = JSON.parse(content);
|
|
26
|
-
// Log for debugging (can be removed later or controlled by verbose flag)
|
|
27
|
-
// console.log('Loading config from:', this.configFile);
|
|
28
26
|
if (!json || typeof json !== 'object') {
|
|
29
27
|
console.warn('Invalid config file structure. Expected JSON object.');
|
|
30
|
-
return {
|
|
28
|
+
return { mcpServers: {} };
|
|
31
29
|
}
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
// Only accept standard MCP format: { mcpServers: { ... } }
|
|
31
|
+
if (!json.mcpServers || typeof json.mcpServers !== 'object') {
|
|
32
|
+
console.warn('Invalid config format. Expected { mcpServers: { ... } }');
|
|
33
|
+
return { mcpServers: {} };
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
type: config.type || (config.command ? 'stdio' : undefined), // Auto-detect type
|
|
42
|
-
...config
|
|
43
|
-
}));
|
|
44
|
-
}
|
|
45
|
-
const validServers = [];
|
|
46
|
-
for (const server of servers) {
|
|
47
|
-
const result = ServerConfigSchema.safeParse(server);
|
|
35
|
+
// Validate each server config
|
|
36
|
+
const validServers = {};
|
|
37
|
+
for (const [name, serverConfig] of Object.entries(json.mcpServers)) {
|
|
38
|
+
const result = ServerConfigSchema.safeParse(serverConfig);
|
|
48
39
|
if (result.success) {
|
|
49
|
-
validServers
|
|
40
|
+
validServers[name] = result.data;
|
|
50
41
|
}
|
|
51
42
|
else {
|
|
52
|
-
|
|
53
|
-
if (server.name) {
|
|
54
|
-
console.warn(`Skipping invalid server config "${server.name}":`, result.error.errors[0]?.message);
|
|
55
|
-
}
|
|
43
|
+
console.warn(`Skipping invalid server config "${name}":`, result.error.errors[0]?.message);
|
|
56
44
|
}
|
|
57
45
|
}
|
|
58
|
-
return {
|
|
46
|
+
return { mcpServers: validServers };
|
|
59
47
|
}
|
|
60
48
|
catch (error) {
|
|
61
49
|
console.error('Failed to parse config file:', error);
|
|
62
|
-
return {
|
|
50
|
+
return { mcpServers: {} };
|
|
63
51
|
}
|
|
64
52
|
}
|
|
65
53
|
saveConfig(config) {
|
|
@@ -67,43 +55,47 @@ export class ConfigManager {
|
|
|
67
55
|
fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2), 'utf-8');
|
|
68
56
|
}
|
|
69
57
|
listServers() {
|
|
70
|
-
|
|
58
|
+
const config = this.loadConfig();
|
|
59
|
+
return Object.entries(config.mcpServers).map(([name, server]) => ({
|
|
60
|
+
name,
|
|
61
|
+
...server,
|
|
62
|
+
}));
|
|
71
63
|
}
|
|
72
64
|
getServer(name) {
|
|
73
65
|
const config = this.loadConfig();
|
|
74
|
-
|
|
66
|
+
const server = config.mcpServers[name];
|
|
67
|
+
if (!server)
|
|
68
|
+
return undefined;
|
|
69
|
+
return { name, ...server };
|
|
75
70
|
}
|
|
76
|
-
addServer(server) {
|
|
71
|
+
addServer(name, server) {
|
|
77
72
|
const config = this.loadConfig();
|
|
78
|
-
if (config.
|
|
79
|
-
throw new Error(`Server with name "${
|
|
73
|
+
if (config.mcpServers[name]) {
|
|
74
|
+
throw new Error(`Server with name "${name}" already exists.`);
|
|
80
75
|
}
|
|
81
|
-
config.
|
|
76
|
+
config.mcpServers[name] = server;
|
|
82
77
|
this.saveConfig(config);
|
|
83
78
|
}
|
|
84
79
|
removeServer(name) {
|
|
85
80
|
const config = this.loadConfig();
|
|
86
|
-
|
|
87
|
-
config.servers = config.servers.filter(s => s.name !== name);
|
|
88
|
-
if (config.servers.length === initialLength) {
|
|
81
|
+
if (!config.mcpServers[name]) {
|
|
89
82
|
throw new Error(`Server with name "${name}" not found.`);
|
|
90
83
|
}
|
|
84
|
+
delete config.mcpServers[name];
|
|
91
85
|
this.saveConfig(config);
|
|
92
86
|
}
|
|
93
87
|
updateServer(name, updates) {
|
|
94
88
|
const config = this.loadConfig();
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
89
|
+
const current = config.mcpServers[name];
|
|
90
|
+
if (!current) {
|
|
97
91
|
throw new Error(`Server with name "${name}" not found.`);
|
|
98
92
|
}
|
|
99
|
-
const current = config.servers[index];
|
|
100
93
|
const updated = { ...current, ...updates };
|
|
101
|
-
// Validate the updated object matches the schema (especially type consistency)
|
|
102
94
|
const result = ServerConfigSchema.safeParse(updated);
|
|
103
95
|
if (!result.success) {
|
|
104
96
|
throw new Error(`Invalid update: ${result.error.message}`);
|
|
105
97
|
}
|
|
106
|
-
config.
|
|
98
|
+
config.mcpServers[name] = result.data;
|
|
107
99
|
this.saveConfig(config);
|
|
108
100
|
}
|
|
109
101
|
}
|
|
@@ -26,74 +26,75 @@ describe('ConfigManager', () => {
|
|
|
26
26
|
});
|
|
27
27
|
describe('addServer', () => {
|
|
28
28
|
it('should add a stdio server', () => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
type: 'stdio',
|
|
29
|
+
const serverName = 'test-stdio';
|
|
30
|
+
const serverConfig = {
|
|
32
31
|
command: 'node',
|
|
33
32
|
args: ['--version']
|
|
34
33
|
};
|
|
35
|
-
manager.addServer(
|
|
34
|
+
manager.addServer(serverName, serverConfig);
|
|
36
35
|
const servers = manager.listServers();
|
|
37
36
|
expect(servers).toHaveLength(1);
|
|
38
|
-
expect(servers[0]).
|
|
37
|
+
expect(servers[0].name).toBe(serverName);
|
|
38
|
+
expect(servers[0].command).toBe('node');
|
|
39
|
+
expect(servers[0].args).toEqual(['--version']);
|
|
39
40
|
});
|
|
40
41
|
it('should add an sse server', () => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
type: 'sse',
|
|
42
|
+
const serverName = 'test-sse';
|
|
43
|
+
const serverConfig = {
|
|
44
44
|
url: 'http://localhost:3000/sse'
|
|
45
45
|
};
|
|
46
|
-
manager.addServer(
|
|
46
|
+
manager.addServer(serverName, serverConfig);
|
|
47
47
|
const retrieved = manager.getServer('test-sse');
|
|
48
|
-
expect(retrieved).
|
|
48
|
+
expect(retrieved).toBeDefined();
|
|
49
|
+
expect(retrieved.name).toBe(serverName);
|
|
50
|
+
expect(retrieved.url).toBe('http://localhost:3000/sse');
|
|
49
51
|
});
|
|
50
52
|
it('should add an http server', () => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
type: 'http',
|
|
53
|
+
const serverName = 'test-http';
|
|
54
|
+
const serverConfig = {
|
|
54
55
|
url: 'http://localhost:3000/mcp'
|
|
55
56
|
};
|
|
56
|
-
manager.addServer(
|
|
57
|
+
manager.addServer(serverName, serverConfig);
|
|
57
58
|
const retrieved = manager.getServer('test-http');
|
|
58
|
-
expect(retrieved).
|
|
59
|
+
expect(retrieved).toBeDefined();
|
|
60
|
+
expect(retrieved.name).toBe(serverName);
|
|
61
|
+
expect(retrieved.url).toBe('http://localhost:3000/mcp');
|
|
59
62
|
});
|
|
60
63
|
it('should add server with disabled flag', () => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
type: 'stdio',
|
|
64
|
+
const serverName = 'disabled-server';
|
|
65
|
+
const serverConfig = {
|
|
64
66
|
command: 'node',
|
|
65
67
|
args: [],
|
|
66
68
|
disabled: true
|
|
67
69
|
};
|
|
68
|
-
manager.addServer(
|
|
70
|
+
manager.addServer(serverName, serverConfig);
|
|
69
71
|
const retrieved = manager.getServer('disabled-server');
|
|
70
|
-
expect(retrieved).
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
72
|
+
expect(retrieved).toBeDefined();
|
|
73
|
+
expect(retrieved.name).toBe('disabled-server');
|
|
74
|
+
expect(retrieved.disabled).toBe(true);
|
|
74
75
|
});
|
|
75
76
|
it('should throw error when adding duplicate server', () => {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
type: 'stdio',
|
|
77
|
+
const serverName = 'duplicate';
|
|
78
|
+
const serverConfig = {
|
|
79
79
|
command: 'node',
|
|
80
80
|
args: []
|
|
81
81
|
};
|
|
82
|
-
manager.addServer(
|
|
83
|
-
expect(() => manager.addServer(
|
|
82
|
+
manager.addServer(serverName, serverConfig);
|
|
83
|
+
expect(() => manager.addServer(serverName, serverConfig)).toThrow('already exists');
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
describe('getServer', () => {
|
|
87
87
|
it('should retrieve existing server', () => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
type: 'stdio',
|
|
88
|
+
const serverName = 'to-retrieve';
|
|
89
|
+
const serverConfig = {
|
|
91
90
|
command: 'node',
|
|
92
91
|
args: []
|
|
93
92
|
};
|
|
94
|
-
manager.addServer(
|
|
93
|
+
manager.addServer(serverName, serverConfig);
|
|
95
94
|
const retrieved = manager.getServer('to-retrieve');
|
|
96
|
-
expect(retrieved).
|
|
95
|
+
expect(retrieved).toBeDefined();
|
|
96
|
+
expect(retrieved.name).toBe(serverName);
|
|
97
|
+
expect(retrieved.command).toBe('node');
|
|
97
98
|
});
|
|
98
99
|
it('should return undefined for non-existing server', () => {
|
|
99
100
|
const retrieved = manager.getServer('non-existing');
|
|
@@ -106,34 +107,25 @@ describe('ConfigManager', () => {
|
|
|
106
107
|
expect(servers).toEqual([]);
|
|
107
108
|
});
|
|
108
109
|
it('should return all servers including disabled ones', () => {
|
|
109
|
-
|
|
110
|
-
name: 'server-1',
|
|
111
|
-
type: 'stdio',
|
|
110
|
+
manager.addServer('server-1', {
|
|
112
111
|
command: 'node',
|
|
113
112
|
args: []
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
name: 'server-2',
|
|
117
|
-
type: 'stdio',
|
|
113
|
+
});
|
|
114
|
+
manager.addServer('server-2', {
|
|
118
115
|
command: 'npm',
|
|
119
116
|
args: ['start'],
|
|
120
117
|
disabled: true
|
|
121
|
-
};
|
|
122
|
-
manager.addServer(server1);
|
|
123
|
-
manager.addServer(server2);
|
|
118
|
+
});
|
|
124
119
|
const servers = manager.listServers();
|
|
125
120
|
expect(servers).toHaveLength(2);
|
|
126
121
|
});
|
|
127
122
|
});
|
|
128
123
|
describe('removeServer', () => {
|
|
129
124
|
it('should remove existing server', () => {
|
|
130
|
-
|
|
131
|
-
name: 'to-remove',
|
|
132
|
-
type: 'stdio',
|
|
125
|
+
manager.addServer('to-remove', {
|
|
133
126
|
command: 'node',
|
|
134
127
|
args: []
|
|
135
|
-
};
|
|
136
|
-
manager.addServer(server);
|
|
128
|
+
});
|
|
137
129
|
expect(manager.listServers()).toHaveLength(1);
|
|
138
130
|
manager.removeServer('to-remove');
|
|
139
131
|
expect(manager.listServers()).toHaveLength(0);
|
|
@@ -144,75 +136,83 @@ describe('ConfigManager', () => {
|
|
|
144
136
|
});
|
|
145
137
|
describe('updateServer', () => {
|
|
146
138
|
it('should update server configuration', () => {
|
|
147
|
-
|
|
148
|
-
name: 'to-update',
|
|
149
|
-
type: 'stdio',
|
|
139
|
+
manager.addServer('to-update', {
|
|
150
140
|
command: 'node',
|
|
151
141
|
args: ['--version']
|
|
152
|
-
};
|
|
153
|
-
manager.
|
|
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',
|
|
142
|
+
});
|
|
143
|
+
manager.updateServer('to-update', {
|
|
162
144
|
command: 'npm',
|
|
163
145
|
args: ['start']
|
|
164
146
|
});
|
|
147
|
+
const updated = manager.getServer('to-update');
|
|
148
|
+
expect(updated).toBeDefined();
|
|
149
|
+
expect(updated.command).toBe('npm');
|
|
150
|
+
expect(updated.args).toEqual(['start']);
|
|
165
151
|
});
|
|
166
152
|
it('should preserve disabled status during update', () => {
|
|
167
|
-
|
|
168
|
-
name: 'update-disabled',
|
|
169
|
-
type: 'stdio',
|
|
153
|
+
manager.addServer('update-disabled', {
|
|
170
154
|
command: 'node',
|
|
171
155
|
args: [],
|
|
172
156
|
disabled: true
|
|
173
|
-
};
|
|
174
|
-
manager.addServer(original);
|
|
157
|
+
});
|
|
175
158
|
manager.updateServer('update-disabled', { command: 'npm' });
|
|
176
159
|
const updated = manager.getServer('update-disabled');
|
|
177
|
-
expect(updated
|
|
178
|
-
expect(updated
|
|
160
|
+
expect(updated.disabled).toBe(true);
|
|
161
|
+
expect(updated.command).toBe('npm');
|
|
179
162
|
});
|
|
180
163
|
it('should throw error when updating non-existing server', () => {
|
|
181
164
|
expect(() => manager.updateServer('non-existing', {})).toThrow('not found');
|
|
182
165
|
});
|
|
183
166
|
});
|
|
184
167
|
describe('persistence', () => {
|
|
185
|
-
it('should persist configuration to file', () => {
|
|
186
|
-
|
|
187
|
-
name: 'persistent',
|
|
188
|
-
type: 'stdio',
|
|
168
|
+
it('should persist configuration to file in standard MCP format', () => {
|
|
169
|
+
manager.addServer('persistent', {
|
|
189
170
|
command: 'node',
|
|
190
171
|
args: []
|
|
191
|
-
};
|
|
192
|
-
manager.addServer(server);
|
|
172
|
+
});
|
|
193
173
|
const configFile = path.join(testConfigDir, 'mcp.json');
|
|
194
174
|
expect(fs.existsSync(configFile)).toBe(true);
|
|
195
175
|
const content = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
196
|
-
|
|
197
|
-
expect(content.
|
|
176
|
+
// Standard MCP format uses mcpServers object, not servers array
|
|
177
|
+
expect(content.mcpServers).toBeDefined();
|
|
178
|
+
expect(content.mcpServers.persistent).toBeDefined();
|
|
179
|
+
expect(content.mcpServers.persistent.command).toBe('node');
|
|
198
180
|
});
|
|
199
181
|
it('should load existing configuration on instantiation', () => {
|
|
200
182
|
const configFile = path.join(testConfigDir, 'mcp.json');
|
|
183
|
+
// Standard MCP format
|
|
201
184
|
const existingConfig = {
|
|
185
|
+
mcpServers: {
|
|
186
|
+
existing: {
|
|
187
|
+
command: 'node',
|
|
188
|
+
args: []
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
fs.writeFileSync(configFile, JSON.stringify(existingConfig, null, 2));
|
|
193
|
+
const newManager = new ConfigManager(testConfigDir);
|
|
194
|
+
const servers = newManager.listServers();
|
|
195
|
+
expect(servers).toHaveLength(1);
|
|
196
|
+
expect(servers[0].name).toBe('existing');
|
|
197
|
+
});
|
|
198
|
+
it('should reject old format (servers array)', () => {
|
|
199
|
+
const configFile = path.join(testConfigDir, 'mcp.json');
|
|
200
|
+
// Old format (should be rejected)
|
|
201
|
+
const oldConfig = {
|
|
202
202
|
servers: [
|
|
203
203
|
{
|
|
204
|
-
name: '
|
|
204
|
+
name: 'old-server',
|
|
205
205
|
type: 'stdio',
|
|
206
206
|
command: 'node',
|
|
207
207
|
args: []
|
|
208
208
|
}
|
|
209
209
|
]
|
|
210
210
|
};
|
|
211
|
-
fs.writeFileSync(configFile, JSON.stringify(
|
|
211
|
+
fs.writeFileSync(configFile, JSON.stringify(oldConfig, null, 2));
|
|
212
212
|
const newManager = new ConfigManager(testConfigDir);
|
|
213
213
|
const servers = newManager.listServers();
|
|
214
|
-
|
|
215
|
-
expect(servers
|
|
214
|
+
// Old format should be rejected, returning empty list
|
|
215
|
+
expect(servers).toHaveLength(0);
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
});
|
package/dist/types/config.js
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
// Standard MCP server configuration (stdio type)
|
|
3
|
+
export const StdioServerConfigSchema = z.object({
|
|
4
|
+
command: z.string(),
|
|
5
|
+
args: z.array(z.string()).optional(),
|
|
6
|
+
env: z.record(z.string()).optional(),
|
|
7
|
+
cwd: z.string().optional(),
|
|
8
|
+
disabled: z.boolean().optional(),
|
|
9
|
+
autoApprove: z.array(z.string()).optional(),
|
|
10
|
+
}).passthrough();
|
|
11
|
+
// Standard MCP server configuration (sse/http type)
|
|
12
|
+
export const HttpServerConfigSchema = z.object({
|
|
13
|
+
url: z.string().url(),
|
|
14
|
+
disabled: z.boolean().optional(),
|
|
15
|
+
autoApprove: z.array(z.string()).optional(),
|
|
16
|
+
}).passthrough();
|
|
17
|
+
// Union of all server config types
|
|
2
18
|
export const ServerConfigSchema = z.union([
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type: z.literal('stdio').optional().default('stdio'),
|
|
6
|
-
command: z.string(),
|
|
7
|
-
args: z.array(z.string()).optional().default([]),
|
|
8
|
-
env: z.record(z.string()).optional(),
|
|
9
|
-
}).passthrough(),
|
|
10
|
-
z.object({
|
|
11
|
-
name: z.string(),
|
|
12
|
-
type: z.literal('sse'),
|
|
13
|
-
url: z.string().url(),
|
|
14
|
-
}).passthrough(),
|
|
15
|
-
z.object({
|
|
16
|
-
name: z.string(),
|
|
17
|
-
type: z.literal('http'),
|
|
18
|
-
url: z.string().url(),
|
|
19
|
-
}).passthrough(),
|
|
19
|
+
StdioServerConfigSchema,
|
|
20
|
+
HttpServerConfigSchema,
|
|
20
21
|
]);
|
|
22
|
+
// Standard MCP config format (mcpServers)
|
|
21
23
|
export const ConfigSchema = z.object({
|
|
22
|
-
|
|
24
|
+
mcpServers: z.record(z.string(), ServerConfigSchema),
|
|
23
25
|
});
|
|
26
|
+
// Helper to detect server type from config
|
|
27
|
+
export function detectServerType(config) {
|
|
28
|
+
if ('url' in config && typeof config.url === 'string') {
|
|
29
|
+
return config.url.includes('/sse') || config.url.endsWith('/sse') ? 'sse' : 'http';
|
|
30
|
+
}
|
|
31
|
+
return 'stdio';
|
|
32
|
+
}
|