mcpsmgr 0.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1631 -0
- package/dist/index.js.map +1 -0
- package/docs/README_zh-CN.md +99 -0
- package/openspec/changes/archive/2026-03-12-fix-global-mcp-default-selection/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-12-fix-global-mcp-default-selection/design.md +41 -0
- package/openspec/changes/archive/2026-03-12-fix-global-mcp-default-selection/proposal.md +28 -0
- package/openspec/changes/archive/2026-03-12-fix-global-mcp-default-selection/specs/project-operations/spec.md +53 -0
- package/openspec/changes/archive/2026-03-12-fix-global-mcp-default-selection/tasks.md +9 -0
- package/openspec/changes/archive/2026-03-12-fix-init-server-detection/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-12-fix-init-server-detection/design.md +40 -0
- package/openspec/changes/archive/2026-03-12-fix-init-server-detection/proposal.md +25 -0
- package/openspec/changes/archive/2026-03-12-fix-init-server-detection/specs/project-operations/spec.md +25 -0
- package/openspec/changes/archive/2026-03-12-fix-init-server-detection/tasks.md +10 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/design.md +32 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/proposal.md +25 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/specs/project-operations/spec.md +30 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/specs/server-management/spec.md +15 -0
- package/openspec/changes/archive/2026-03-12-graceful-exit-on-interrupt/tasks.md +17 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/design.md +104 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/proposal.md +34 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/specs/agent-adapters/spec.md +110 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/specs/central-storage/spec.md +38 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/specs/glm-integration/spec.md +66 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/specs/project-operations/spec.md +76 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/specs/server-management/spec.md +75 -0
- package/openspec/changes/archive/2026-03-12-mcps-manager-cli/tasks.md +60 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/agent-adapters/spec.md +148 -0
- package/openspec/specs/central-storage/spec.md +42 -0
- package/openspec/specs/glm-integration/spec.md +70 -0
- package/openspec/specs/project-operations/spec.md +138 -0
- package/openspec/specs/server-management/spec.md +93 -0
- package/package.json +33 -0
- package/src/__tests__/integration.test.ts +200 -0
- package/src/adapters/__tests__/adapters.test.ts +274 -0
- package/src/adapters/antigravity.ts +114 -0
- package/src/adapters/claude-code.ts +114 -0
- package/src/adapters/codex-cli.ts +135 -0
- package/src/adapters/env-args.ts +51 -0
- package/src/adapters/gemini-cli.ts +110 -0
- package/src/adapters/index.ts +32 -0
- package/src/adapters/json-file.ts +24 -0
- package/src/adapters/opencode.ts +114 -0
- package/src/commands/add.ts +68 -0
- package/src/commands/init.ts +136 -0
- package/src/commands/list.ts +77 -0
- package/src/commands/remove.ts +61 -0
- package/src/commands/server-add.ts +211 -0
- package/src/commands/server-list.ts +24 -0
- package/src/commands/server-remove.ts +12 -0
- package/src/commands/setup.ts +71 -0
- package/src/commands/sync.ts +98 -0
- package/src/index.ts +100 -0
- package/src/services/glm-client.ts +190 -0
- package/src/services/system-prompt.ts +61 -0
- package/src/services/web-reader.ts +130 -0
- package/src/types.ts +59 -0
- package/src/utils/config.ts +22 -0
- package/src/utils/paths.ts +11 -0
- package/src/utils/prompt.ts +3 -0
- package/src/utils/resolve-config.ts +13 -0
- package/src/utils/server-store.ts +56 -0
- package/tsconfig.json +17 -0
- package/tsup.config.ts +13 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { allAdapters } from "../adapters/index.js";
|
|
2
|
+
import type { AgentAdapter } from "../types.js";
|
|
3
|
+
|
|
4
|
+
interface ServerEntry {
|
|
5
|
+
transport: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function listCommand(): Promise<void> {
|
|
9
|
+
const projectDir = process.cwd();
|
|
10
|
+
|
|
11
|
+
const matrix: Record<string, Record<string, ServerEntry>> = {};
|
|
12
|
+
const activeAdapters: AgentAdapter[] = [];
|
|
13
|
+
|
|
14
|
+
for (const adapter of allAdapters) {
|
|
15
|
+
let servers: Record<string, unknown>;
|
|
16
|
+
try {
|
|
17
|
+
servers = await adapter.read(projectDir);
|
|
18
|
+
} catch {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Object.keys(servers).length === 0) continue;
|
|
23
|
+
|
|
24
|
+
activeAdapters.push(adapter);
|
|
25
|
+
for (const [name, raw] of Object.entries(servers)) {
|
|
26
|
+
if (!matrix[name]) {
|
|
27
|
+
matrix[name] = {};
|
|
28
|
+
}
|
|
29
|
+
const config = adapter.fromAgentFormat(
|
|
30
|
+
name,
|
|
31
|
+
raw as Record<string, unknown>,
|
|
32
|
+
);
|
|
33
|
+
matrix[name][adapter.id] = {
|
|
34
|
+
transport: config?.transport ?? "?",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (Object.keys(matrix).length === 0) {
|
|
40
|
+
console.log(
|
|
41
|
+
"No MCP servers found in any agent configuration. Use \"mcpsmgr init\" to get started.",
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const serverNames = Object.keys(matrix).sort();
|
|
47
|
+
const agentIds = activeAdapters.map((a) => a.id);
|
|
48
|
+
const agentNames = activeAdapters.map((a) => a.name);
|
|
49
|
+
|
|
50
|
+
const colWidths = [
|
|
51
|
+
Math.max(6, ...serverNames.map((n) => n.length)),
|
|
52
|
+
...agentNames.map((n) => Math.max(n.length, 5)),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const header = [
|
|
56
|
+
"Server".padEnd(colWidths.at(0) ?? 6),
|
|
57
|
+
...agentNames.map((n, i) => n.padEnd(colWidths.at(i + 1) ?? 5)),
|
|
58
|
+
].join(" ");
|
|
59
|
+
|
|
60
|
+
const separator = colWidths.map((w) => "-".repeat(w)).join(" ");
|
|
61
|
+
|
|
62
|
+
console.log(`\n${header}`);
|
|
63
|
+
console.log(separator);
|
|
64
|
+
|
|
65
|
+
for (const name of serverNames) {
|
|
66
|
+
const cells = [
|
|
67
|
+
name.padEnd(colWidths.at(0) ?? 6),
|
|
68
|
+
...agentIds.map((id, i) => {
|
|
69
|
+
const entry = matrix[name][id];
|
|
70
|
+
const val = entry ? entry.transport : "-";
|
|
71
|
+
return val.padEnd(colWidths.at(i + 1) ?? 5);
|
|
72
|
+
}),
|
|
73
|
+
];
|
|
74
|
+
console.log(cells.join(" "));
|
|
75
|
+
}
|
|
76
|
+
console.log();
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { checkbox } from "@inquirer/prompts";
|
|
2
|
+
import { allAdapters } from "../adapters/index.js";
|
|
3
|
+
import { isUserCancellation } from "../utils/prompt.js";
|
|
4
|
+
import type { AgentAdapter } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export async function removeCommand(serverName: string): Promise<void> {
|
|
7
|
+
try {
|
|
8
|
+
await removeCommandInner(serverName);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (isUserCancellation(error)) return;
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function removeCommandInner(serverName: string): Promise<void> {
|
|
16
|
+
const projectDir = process.cwd();
|
|
17
|
+
|
|
18
|
+
const agentsWithServer: AgentAdapter[] = [];
|
|
19
|
+
for (const adapter of allAdapters) {
|
|
20
|
+
try {
|
|
21
|
+
const has = await adapter.has(projectDir, serverName);
|
|
22
|
+
if (has) {
|
|
23
|
+
agentsWithServer.push(adapter);
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// config file doesn't exist, skip
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (agentsWithServer.length === 0) {
|
|
31
|
+
console.log(
|
|
32
|
+
`Server "${serverName}" not found in any agent configuration.`,
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const selectedAgents = await checkbox<AgentAdapter>({
|
|
38
|
+
message: `Remove "${serverName}" from which agents?`,
|
|
39
|
+
choices: agentsWithServer.map((adapter) => ({
|
|
40
|
+
name: `${adapter.name}${adapter.isGlobal ? " [global]" : ""}`,
|
|
41
|
+
value: adapter,
|
|
42
|
+
checked: !adapter.isGlobal,
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (selectedAgents.length === 0) {
|
|
47
|
+
console.log("No agents selected.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const agent of selectedAgents) {
|
|
52
|
+
try {
|
|
53
|
+
await agent.remove(projectDir, serverName);
|
|
54
|
+
console.log(` - ${serverName} <- ${agent.name}`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn(
|
|
57
|
+
` ! ${serverName} <- ${agent.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { confirm, input, password } from "@inquirer/prompts";
|
|
2
|
+
import type { DefaultConfig, ServerDefinition, StdioConfig } from "../types.js";
|
|
3
|
+
import { readGlobalConfig } from "../utils/config.js";
|
|
4
|
+
import {
|
|
5
|
+
serverExists,
|
|
6
|
+
writeServerDefinition,
|
|
7
|
+
} from "../utils/server-store.js";
|
|
8
|
+
import {
|
|
9
|
+
isValidInput,
|
|
10
|
+
buildUserMessage,
|
|
11
|
+
analyzeWithGlm,
|
|
12
|
+
type AnalysisResult,
|
|
13
|
+
} from "../services/glm-client.js";
|
|
14
|
+
import { isUserCancellation } from "../utils/prompt.js";
|
|
15
|
+
|
|
16
|
+
export async function serverAddCommand(source?: string): Promise<void> {
|
|
17
|
+
try {
|
|
18
|
+
await serverAddCommandInner(source);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (isUserCancellation(error)) return;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function serverAddCommandInner(source?: string): Promise<void> {
|
|
26
|
+
const urlInput =
|
|
27
|
+
source ??
|
|
28
|
+
(await input({
|
|
29
|
+
message: "Enter MCP server URL or GitHub owner/repo (leave empty for manual):",
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
if (urlInput.trim() === "") {
|
|
33
|
+
await manualAddFlow();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const validation = isValidInput(urlInput.trim());
|
|
38
|
+
if (!validation.valid) {
|
|
39
|
+
console.error(`Error: ${validation.reason}`);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = await readGlobalConfig();
|
|
45
|
+
|
|
46
|
+
console.log("Analyzing documentation with GLM5...");
|
|
47
|
+
let analysis: AnalysisResult;
|
|
48
|
+
try {
|
|
49
|
+
const userMessage = buildUserMessage(urlInput.trim());
|
|
50
|
+
analysis = await analyzeWithGlm(config, userMessage);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(
|
|
53
|
+
`GLM5 analysis failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
54
|
+
);
|
|
55
|
+
const fallback = await confirm({
|
|
56
|
+
message: "Would you like to configure manually instead?",
|
|
57
|
+
});
|
|
58
|
+
if (fallback) {
|
|
59
|
+
await manualAddFlow();
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
displayAnalysisResult(analysis, urlInput.trim());
|
|
65
|
+
|
|
66
|
+
const trust = await confirm({
|
|
67
|
+
message: "Trust this analysis result?",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!trust) {
|
|
71
|
+
const manual = await confirm({
|
|
72
|
+
message: "Configure manually instead?",
|
|
73
|
+
});
|
|
74
|
+
if (manual) {
|
|
75
|
+
await manualAddFlow();
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (serverExists(analysis.name)) {
|
|
81
|
+
console.error(
|
|
82
|
+
`Error: Server "${analysis.name}" already exists. Run "mcpsmgr server remove ${analysis.name}" first.`,
|
|
83
|
+
);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const env: Record<string, string> = {};
|
|
89
|
+
for (const varName of analysis.requiredEnvVars) {
|
|
90
|
+
const value = await password({
|
|
91
|
+
message: `Enter value for ${varName} (stored locally, never sent to servers):`,
|
|
92
|
+
mask: "*",
|
|
93
|
+
});
|
|
94
|
+
env[varName] = value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const defaultConfig: DefaultConfig =
|
|
98
|
+
analysis.default.transport === "stdio"
|
|
99
|
+
? {
|
|
100
|
+
transport: "stdio",
|
|
101
|
+
command: analysis.default.command ?? "",
|
|
102
|
+
args: [...(analysis.default.args ?? [])],
|
|
103
|
+
env: { ...(analysis.default.env ?? {}), ...env },
|
|
104
|
+
}
|
|
105
|
+
: {
|
|
106
|
+
transport: "http",
|
|
107
|
+
url: analysis.default.url ?? "",
|
|
108
|
+
headers: { ...(analysis.default.headers ?? {}) },
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const definition: ServerDefinition = {
|
|
112
|
+
name: analysis.name,
|
|
113
|
+
source: urlInput.trim(),
|
|
114
|
+
default: defaultConfig,
|
|
115
|
+
overrides: analysis.overrides as ServerDefinition["overrides"],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
await writeServerDefinition(definition);
|
|
119
|
+
console.log(`Server "${analysis.name}" saved to central repository.`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function displayAnalysisResult(result: AnalysisResult, source: string): void {
|
|
123
|
+
console.log("\n--- Analysis Result ---");
|
|
124
|
+
console.log(`Name: ${result.name}`);
|
|
125
|
+
console.log(`Source: ${source}`);
|
|
126
|
+
console.log(`Transport: ${result.default.transport}`);
|
|
127
|
+
if (result.default.transport === "stdio") {
|
|
128
|
+
console.log(`Command: ${result.default.command}`);
|
|
129
|
+
console.log(`Args: ${JSON.stringify(result.default.args)}`);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(`URL: ${result.default.url}`);
|
|
132
|
+
}
|
|
133
|
+
if (Object.keys(result.overrides).length > 0) {
|
|
134
|
+
console.log("Agent overrides:");
|
|
135
|
+
for (const [agent, override] of Object.entries(result.overrides)) {
|
|
136
|
+
console.log(` ${agent}: ${JSON.stringify(override)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (result.requiredEnvVars.length > 0) {
|
|
140
|
+
console.log(
|
|
141
|
+
`Required env vars: ${result.requiredEnvVars.join(", ")}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
console.log("---\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function manualAddFlow(): Promise<void> {
|
|
148
|
+
const name = await input({
|
|
149
|
+
message: "Server name (kebab-case):",
|
|
150
|
+
validate: (v) =>
|
|
151
|
+
/^[a-z][a-z0-9-]*$/.test(v.trim()) ? true : "Must be kebab-case",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (serverExists(name.trim())) {
|
|
155
|
+
console.error(
|
|
156
|
+
`Error: Server "${name.trim()}" already exists. Run "mcpsmgr server remove ${name.trim()}" first.`,
|
|
157
|
+
);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const source = await input({
|
|
163
|
+
message: "Source URL (optional):",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const command = await input({
|
|
167
|
+
message: "Command (e.g., npx):",
|
|
168
|
+
validate: (v) => (v.trim().length > 0 ? true : "Command is required"),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const argsStr = await input({
|
|
172
|
+
message: "Args (comma-separated, e.g., -y,@scope/package):",
|
|
173
|
+
});
|
|
174
|
+
const args = argsStr
|
|
175
|
+
.trim()
|
|
176
|
+
.split(",")
|
|
177
|
+
.map((a) => a.trim())
|
|
178
|
+
.filter((a) => a.length > 0);
|
|
179
|
+
|
|
180
|
+
const envPairs: Record<string, string> = {};
|
|
181
|
+
let addMore = true;
|
|
182
|
+
while (addMore) {
|
|
183
|
+
const envName = await input({
|
|
184
|
+
message: "Env var name (leave empty to finish):",
|
|
185
|
+
});
|
|
186
|
+
if (envName.trim() === "") break;
|
|
187
|
+
const envValue = await password({
|
|
188
|
+
message: `Value for ${envName.trim()} (stored locally, never sent to servers):`,
|
|
189
|
+
mask: "*",
|
|
190
|
+
});
|
|
191
|
+
envPairs[envName.trim()] = envValue;
|
|
192
|
+
addMore = true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const config: StdioConfig = {
|
|
196
|
+
transport: "stdio",
|
|
197
|
+
command: command.trim(),
|
|
198
|
+
args,
|
|
199
|
+
env: envPairs,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const definition: ServerDefinition = {
|
|
203
|
+
name: name.trim(),
|
|
204
|
+
source: source.trim(),
|
|
205
|
+
default: config,
|
|
206
|
+
overrides: {},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await writeServerDefinition(definition);
|
|
210
|
+
console.log(`Server "${name.trim()}" saved to central repository.`);
|
|
211
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { listServerDefinitions } from "../utils/server-store.js";
|
|
2
|
+
|
|
3
|
+
export async function serverListCommand(): Promise<void> {
|
|
4
|
+
const servers = await listServerDefinitions();
|
|
5
|
+
|
|
6
|
+
if (servers.length === 0) {
|
|
7
|
+
console.log("No servers in central repository. Use \"mcpsmgr server add\" to add one.");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
console.log("\nCentral Repository Servers:\n");
|
|
12
|
+
for (const server of servers) {
|
|
13
|
+
const overrideCount = Object.keys(server.overrides).length;
|
|
14
|
+
const overrideInfo =
|
|
15
|
+
overrideCount > 0 ? ` (${overrideCount} overrides)` : "";
|
|
16
|
+
console.log(
|
|
17
|
+
` ${server.name} [${server.default.transport}]${overrideInfo}`,
|
|
18
|
+
);
|
|
19
|
+
if (server.source) {
|
|
20
|
+
console.log(` source: ${server.source}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
console.log();
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { removeServerDefinition, serverExists } from "../utils/server-store.js";
|
|
2
|
+
|
|
3
|
+
export async function serverRemoveCommand(name: string): Promise<void> {
|
|
4
|
+
if (!serverExists(name)) {
|
|
5
|
+
console.error(`Error: Server "${name}" does not exist in central repository.`);
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
await removeServerDefinition(name);
|
|
11
|
+
console.log(`Server "${name}" removed from central repository.`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
4
|
+
import type { GlobalConfig } from "../types.js";
|
|
5
|
+
import { paths } from "../utils/paths.js";
|
|
6
|
+
import { writeGlobalConfig, configExists } from "../utils/config.js";
|
|
7
|
+
import { isUserCancellation } from "../utils/prompt.js";
|
|
8
|
+
|
|
9
|
+
const GLM_ENDPOINTS = [
|
|
10
|
+
{
|
|
11
|
+
name: "Coding Plan (GLM-5)",
|
|
12
|
+
value: "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "General (GLM-5)",
|
|
16
|
+
value: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
|
|
17
|
+
},
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
export async function setupCommand(): Promise<void> {
|
|
21
|
+
try {
|
|
22
|
+
await setupCommandInner();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (isUserCancellation(error)) return;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function setupCommandInner(): Promise<void> {
|
|
30
|
+
if (configExists()) {
|
|
31
|
+
const overwrite = await confirm({
|
|
32
|
+
message: "Configuration already exists. Overwrite?",
|
|
33
|
+
});
|
|
34
|
+
if (!overwrite) {
|
|
35
|
+
console.log("Setup cancelled.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!existsSync(paths.baseDir)) {
|
|
41
|
+
await mkdir(paths.baseDir, { recursive: true });
|
|
42
|
+
await chmod(paths.baseDir, 0o700);
|
|
43
|
+
}
|
|
44
|
+
if (!existsSync(paths.serversDir)) {
|
|
45
|
+
await mkdir(paths.serversDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const glmApiKey = await input({
|
|
49
|
+
message: "Enter GLM5 API Key:",
|
|
50
|
+
validate: (v) => (v.trim().length > 0 ? true : "API key is required"),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const glmEndpoint = await select({
|
|
54
|
+
message: "Select GLM5 endpoint:",
|
|
55
|
+
choices: GLM_ENDPOINTS.map((e) => ({ name: e.name, value: e.value })),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const config: GlobalConfig = {
|
|
59
|
+
glm: {
|
|
60
|
+
apiKey: glmApiKey.trim(),
|
|
61
|
+
endpoint: glmEndpoint,
|
|
62
|
+
},
|
|
63
|
+
webReader: {
|
|
64
|
+
apiKey: glmApiKey.trim(),
|
|
65
|
+
url: "https://open.bigmodel.cn/api/mcp/web_reader/mcp",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
await writeGlobalConfig(config);
|
|
70
|
+
console.log("Setup complete. Configuration saved to ~/.mcps-manager/config.json");
|
|
71
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import { allAdapters } from "../adapters/index.js";
|
|
3
|
+
import { listServerDefinitions } from "../utils/server-store.js";
|
|
4
|
+
import { resolveConfig } from "../utils/resolve-config.js";
|
|
5
|
+
import { isUserCancellation } from "../utils/prompt.js";
|
|
6
|
+
|
|
7
|
+
export async function syncCommand(): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
await syncCommandInner();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (isUserCancellation(error)) return;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function syncCommandInner(): Promise<void> {
|
|
17
|
+
const projectDir = process.cwd();
|
|
18
|
+
|
|
19
|
+
const definitions = await listServerDefinitions();
|
|
20
|
+
if (definitions.length === 0) {
|
|
21
|
+
console.log("Central repository is empty. Nothing to sync.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const changes: Array<{
|
|
26
|
+
agentName: string;
|
|
27
|
+
serverName: string;
|
|
28
|
+
action: "update" | "skip";
|
|
29
|
+
reason?: string;
|
|
30
|
+
}> = [];
|
|
31
|
+
|
|
32
|
+
for (const adapter of allAdapters) {
|
|
33
|
+
let servers: Record<string, unknown>;
|
|
34
|
+
try {
|
|
35
|
+
servers = await adapter.read(projectDir);
|
|
36
|
+
} catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const definition of definitions) {
|
|
41
|
+
if (!(definition.name in servers)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const currentRaw = servers[definition.name] as Record<string, unknown>;
|
|
46
|
+
const desired = resolveConfig(definition, adapter);
|
|
47
|
+
const desiredRaw = adapter.toAgentFormat(desired);
|
|
48
|
+
|
|
49
|
+
if (JSON.stringify(currentRaw) !== JSON.stringify(desiredRaw)) {
|
|
50
|
+
changes.push({
|
|
51
|
+
agentName: adapter.name,
|
|
52
|
+
serverName: definition.name,
|
|
53
|
+
action: "update",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (changes.length === 0) {
|
|
60
|
+
console.log("All agent configurations are up to date.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("\nSync preview:");
|
|
65
|
+
for (const change of changes) {
|
|
66
|
+
console.log(` ~ ${change.serverName} -> ${change.agentName}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const proceed = await confirm({ message: "Apply changes?" });
|
|
70
|
+
if (!proceed) {
|
|
71
|
+
console.log("Cancelled.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const adapter of allAdapters) {
|
|
76
|
+
for (const definition of definitions) {
|
|
77
|
+
const relevant = changes.find(
|
|
78
|
+
(c) =>
|
|
79
|
+
c.agentName === adapter.name &&
|
|
80
|
+
c.serverName === definition.name,
|
|
81
|
+
);
|
|
82
|
+
if (!relevant) continue;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await adapter.remove(projectDir, definition.name);
|
|
86
|
+
const config = resolveConfig(definition, adapter);
|
|
87
|
+
await adapter.write(projectDir, definition.name, config);
|
|
88
|
+
console.log(` ~ ${definition.name} -> ${adapter.name} (updated)`);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.warn(
|
|
91
|
+
` ! ${definition.name} -> ${adapter.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log("\nSync complete.");
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import { configExists } from "./utils/config.js";
|
|
3
|
+
import { setupCommand } from "./commands/setup.js";
|
|
4
|
+
import { serverAddCommand } from "./commands/server-add.js";
|
|
5
|
+
import { serverRemoveCommand } from "./commands/server-remove.js";
|
|
6
|
+
import { serverListCommand } from "./commands/server-list.js";
|
|
7
|
+
import { initCommand } from "./commands/init.js";
|
|
8
|
+
import { addCommand } from "./commands/add.js";
|
|
9
|
+
import { removeCommand } from "./commands/remove.js";
|
|
10
|
+
import { syncCommand } from "./commands/sync.js";
|
|
11
|
+
import { listCommand } from "./commands/list.js";
|
|
12
|
+
|
|
13
|
+
function requireSetup(): void {
|
|
14
|
+
if (!configExists()) {
|
|
15
|
+
console.error(
|
|
16
|
+
'mcpsmgr is not configured. Run "mcpsmgr setup" first.',
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.name("mcpsmgr")
|
|
24
|
+
.description("Unified MCP server manager for multiple coding agents")
|
|
25
|
+
.version("0.1.0");
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("setup")
|
|
29
|
+
.description("Initialize mcpsmgr configuration")
|
|
30
|
+
.action(setupCommand);
|
|
31
|
+
|
|
32
|
+
const server = program
|
|
33
|
+
.command("server")
|
|
34
|
+
.description("Manage MCP server definitions in central repository");
|
|
35
|
+
|
|
36
|
+
server
|
|
37
|
+
.command("add [source]")
|
|
38
|
+
.description("Add an MCP server (URL or GitHub owner/repo)")
|
|
39
|
+
.action((source?: string) => {
|
|
40
|
+
requireSetup();
|
|
41
|
+
return serverAddCommand(source);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
server
|
|
45
|
+
.command("remove <name>")
|
|
46
|
+
.description("Remove an MCP server from central repository")
|
|
47
|
+
.action((name: string) => {
|
|
48
|
+
requireSetup();
|
|
49
|
+
return serverRemoveCommand(name);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
server
|
|
53
|
+
.command("list")
|
|
54
|
+
.description("List all servers in central repository")
|
|
55
|
+
.action(() => {
|
|
56
|
+
requireSetup();
|
|
57
|
+
return serverListCommand();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
program
|
|
61
|
+
.command("init")
|
|
62
|
+
.description("Initialize MCP servers for current project")
|
|
63
|
+
.action(() => {
|
|
64
|
+
requireSetup();
|
|
65
|
+
return initCommand();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command("add <server-name>")
|
|
70
|
+
.description("Add a server from central repository to current project")
|
|
71
|
+
.action((serverName: string) => {
|
|
72
|
+
requireSetup();
|
|
73
|
+
return addCommand(serverName);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
program
|
|
77
|
+
.command("remove <server-name>")
|
|
78
|
+
.description("Remove a server from current project agent configs")
|
|
79
|
+
.action((serverName: string) => {
|
|
80
|
+
requireSetup();
|
|
81
|
+
return removeCommand(serverName);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
program
|
|
85
|
+
.command("sync")
|
|
86
|
+
.description("Sync central repository changes to project agent configs")
|
|
87
|
+
.action(() => {
|
|
88
|
+
requireSetup();
|
|
89
|
+
return syncCommand();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
program
|
|
93
|
+
.command("list")
|
|
94
|
+
.description("List MCP servers across all agent configs in current project")
|
|
95
|
+
.action(() => {
|
|
96
|
+
requireSetup();
|
|
97
|
+
return listCommand();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
program.parse();
|