mcpsmgr 0.1.0 → 0.2.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 +4 -4
- package/dist/index.js +132 -12
- package/dist/index.js.map +1 -1
- package/docs/README_zh-CN.md +4 -4
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/design.md +62 -0
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/proposal.md +28 -0
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/specs/agent-adapters/spec.md +10 -0
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/specs/openclaw-adapter/spec.md +64 -0
- package/openspec/changes/archive/2026-03-17-add-openclaw-adapter/tasks.md +18 -0
- package/openspec/specs/agent-adapters/spec.md +4 -4
- package/openspec/specs/openclaw-adapter/spec.md +68 -0
- package/package.json +2 -1
- package/src/__tests__/integration.test.ts +5 -5
- package/src/adapters/__tests__/adapters.test.ts +9 -9
- package/src/adapters/__tests__/json5-file.test.ts +67 -0
- package/src/adapters/__tests__/openclaw.test.ts +218 -0
- package/src/adapters/{codex-cli.ts → codex.ts} +4 -4
- package/src/adapters/index.ts +4 -2
- package/src/adapters/json5-file.ts +25 -0
- package/src/adapters/openclaw.ts +113 -0
- package/src/services/system-prompt.ts +2 -2
- package/src/types.ts +3 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Unified MCP (Model Context Protocol) server manager for multiple AI coding agent
|
|
|
6
6
|
|
|
7
7
|
## Problem
|
|
8
8
|
|
|
9
|
-
Each AI coding agent (Claude Code, Codex
|
|
9
|
+
Each AI coding agent (Claude Code, Codex, Gemini CLI, OpenCode, Antigravity) uses its own config format for MCP servers. Managing the same servers across multiple agents means editing multiple config files manually, which is tedious and error-prone.
|
|
10
10
|
|
|
11
11
|
## Solution
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ Each AI coding agent (Claude Code, Codex CLI, Gemini CLI, OpenCode, Antigravity)
|
|
|
15
15
|
```
|
|
16
16
|
Central Repository Agent Configs
|
|
17
17
|
┌──────────────────┐ ┌─► Claude Code (.claude.json)
|
|
18
|
-
│ server-a (stdio)│───┼─► Codex
|
|
18
|
+
│ server-a (stdio)│───┼─► Codex (.codex/config.toml)
|
|
19
19
|
│ server-b (http) │ ├─► Gemini CLI (.gemini/settings.json)
|
|
20
20
|
│ server-c (stdio)│ ├─► OpenCode (.opencode.json)
|
|
21
21
|
└──────────────────┘ └─► Antigravity (.antigravity/config.json)
|
|
@@ -24,7 +24,7 @@ Central Repository Agent Configs
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
26
|
- **Central server repository** - Define MCP servers once in `~/.mcps-manager/servers/`
|
|
27
|
-
- **Multi-agent support** - Claude Code, Codex
|
|
27
|
+
- **Multi-agent support** - Claude Code, Codex, Gemini CLI, OpenCode, Antigravity
|
|
28
28
|
- **AI-assisted setup** - Provide a URL or GitHub repo, and GLM-5 analyzes the documentation to generate the config automatically
|
|
29
29
|
- **Per-agent overrides** - Customize server config for specific agents when needed
|
|
30
30
|
- **Project-level init** - Deploy selected servers to detected agents in any project
|
|
@@ -81,7 +81,7 @@ mcpsmgr sync
|
|
|
81
81
|
| Agent | Config Location | Format |
|
|
82
82
|
|---|---|---|
|
|
83
83
|
| Claude Code | `.claude.json` (project) | JSON |
|
|
84
|
-
| Codex
|
|
84
|
+
| Codex | `.codex/config.toml` (project) | TOML |
|
|
85
85
|
| Gemini CLI | `.gemini/settings.json` (global) | JSON |
|
|
86
86
|
| OpenCode | `.opencode.json` (project) | JSON |
|
|
87
87
|
| Antigravity | `.antigravity/config.json` (project) | JSON |
|
package/dist/index.js
CHANGED
|
@@ -258,7 +258,7 @@ The 5 agents and their configuration differences:
|
|
|
258
258
|
- HTTP: { "type": "http", "url": "...", "headers": {...} }
|
|
259
259
|
- IMPORTANT: Do NOT use "env" field. Environment variables will be handled separately.
|
|
260
260
|
|
|
261
|
-
2. **Codex
|
|
261
|
+
2. **Codex** (.codex/config.toml)
|
|
262
262
|
- TOML format: command = "...", args = [...]
|
|
263
263
|
- Same key names as Claude Code but in TOML
|
|
264
264
|
- IMPORTANT: Do NOT use "env" field.
|
|
@@ -304,7 +304,7 @@ After analyzing the documentation, return a JSON object with this exact structur
|
|
|
304
304
|
|
|
305
305
|
Rules:
|
|
306
306
|
- "name" should be a kebab-case identifier for the server
|
|
307
|
-
- "default" should be the most common configuration (usually works for Claude Code, Codex
|
|
307
|
+
- "default" should be the most common configuration (usually works for Claude Code, Codex, Gemini CLI)
|
|
308
308
|
- Only add "overrides" for agents that need DIFFERENT configuration from the default
|
|
309
309
|
- OpenCode usually needs an override because its command format differs (array vs string+args)
|
|
310
310
|
- "requiredEnvVars" lists environment variable names the user needs to provide values for
|
|
@@ -630,7 +630,7 @@ async function serverListCommand() {
|
|
|
630
630
|
import { checkbox, confirm as confirm3 } from "@inquirer/prompts";
|
|
631
631
|
|
|
632
632
|
// src/adapters/index.ts
|
|
633
|
-
import { existsSync as
|
|
633
|
+
import { existsSync as existsSync7 } from "fs";
|
|
634
634
|
|
|
635
635
|
// src/adapters/claude-code.ts
|
|
636
636
|
import { join as join2 } from "path";
|
|
@@ -789,7 +789,7 @@ var claudeCodeAdapter = {
|
|
|
789
789
|
}
|
|
790
790
|
};
|
|
791
791
|
|
|
792
|
-
// src/adapters/codex
|
|
792
|
+
// src/adapters/codex.ts
|
|
793
793
|
import { join as join3 } from "path";
|
|
794
794
|
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
|
|
795
795
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -861,9 +861,9 @@ async function writeTomlFile(filePath, data) {
|
|
|
861
861
|
}
|
|
862
862
|
await writeFile4(filePath, stringifyToml(data) + "\n", "utf-8");
|
|
863
863
|
}
|
|
864
|
-
var
|
|
865
|
-
id: "codex
|
|
866
|
-
name: "Codex
|
|
864
|
+
var codexAdapter = {
|
|
865
|
+
id: "codex",
|
|
866
|
+
name: "Codex",
|
|
867
867
|
configPath: (projectDir) => join3(projectDir, ".codex", "config.toml"),
|
|
868
868
|
isGlobal: false,
|
|
869
869
|
toAgentFormat: toAgentFormat2,
|
|
@@ -879,7 +879,7 @@ var codexCliAdapter = {
|
|
|
879
879
|
const servers = parsed["mcp_servers"] ?? {};
|
|
880
880
|
if (serverName in servers) {
|
|
881
881
|
throw new Error(
|
|
882
|
-
`Conflict: "${serverName}" already exists in Codex
|
|
882
|
+
`Conflict: "${serverName}" already exists in Codex config`
|
|
883
883
|
);
|
|
884
884
|
}
|
|
885
885
|
const updated = {
|
|
@@ -1195,20 +1195,140 @@ var antigravityAdapter = {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
};
|
|
1197
1197
|
|
|
1198
|
+
// src/adapters/openclaw.ts
|
|
1199
|
+
import { homedir as homedir3 } from "os";
|
|
1200
|
+
import { join as join7 } from "path";
|
|
1201
|
+
|
|
1202
|
+
// src/adapters/json5-file.ts
|
|
1203
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir6 } from "fs/promises";
|
|
1204
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1205
|
+
import { dirname as dirname3 } from "path";
|
|
1206
|
+
import JSON5 from "json5";
|
|
1207
|
+
async function readJson5File(filePath) {
|
|
1208
|
+
if (!existsSync6(filePath)) {
|
|
1209
|
+
return {};
|
|
1210
|
+
}
|
|
1211
|
+
const raw = await readFile5(filePath, "utf-8");
|
|
1212
|
+
return JSON5.parse(raw);
|
|
1213
|
+
}
|
|
1214
|
+
async function writeJson5File(filePath, data) {
|
|
1215
|
+
const dir = dirname3(filePath);
|
|
1216
|
+
if (!existsSync6(dir)) {
|
|
1217
|
+
await mkdir6(dir, { recursive: true });
|
|
1218
|
+
}
|
|
1219
|
+
await writeFile5(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/adapters/openclaw.ts
|
|
1223
|
+
var GLOBAL_CONFIG_PATH2 = join7(
|
|
1224
|
+
homedir3(),
|
|
1225
|
+
".openclaw",
|
|
1226
|
+
"openclaw.json"
|
|
1227
|
+
);
|
|
1228
|
+
function toAgentFormat6(config) {
|
|
1229
|
+
if (config.transport === "stdio") {
|
|
1230
|
+
const { resolvedArgs, remainingEnv } = resolveEnvInArgs(
|
|
1231
|
+
config.args,
|
|
1232
|
+
config.env
|
|
1233
|
+
);
|
|
1234
|
+
const envArgs = buildEnvArgs(remainingEnv);
|
|
1235
|
+
if (envArgs.length > 0) {
|
|
1236
|
+
return {
|
|
1237
|
+
command: "env",
|
|
1238
|
+
args: [...envArgs, config.command, ...resolvedArgs]
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
command: config.command,
|
|
1243
|
+
args: resolvedArgs
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
url: config.url,
|
|
1248
|
+
headers: { ...config.headers }
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function fromAgentFormat6(_name, raw) {
|
|
1252
|
+
if (raw["command"]) {
|
|
1253
|
+
const command = raw["command"];
|
|
1254
|
+
const rawArgs = raw["args"] ?? [];
|
|
1255
|
+
const legacyEnv = raw["env"];
|
|
1256
|
+
if (legacyEnv && Object.keys(legacyEnv).length > 0) {
|
|
1257
|
+
return { transport: "stdio", command, args: rawArgs, env: legacyEnv };
|
|
1258
|
+
}
|
|
1259
|
+
if (command === "env") {
|
|
1260
|
+
const { env, commandIndex } = parseEnvArgs(rawArgs);
|
|
1261
|
+
return {
|
|
1262
|
+
transport: "stdio",
|
|
1263
|
+
command: rawArgs[commandIndex] ?? "",
|
|
1264
|
+
args: rawArgs.slice(commandIndex + 1),
|
|
1265
|
+
env
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
return { transport: "stdio", command, args: rawArgs, env: {} };
|
|
1269
|
+
}
|
|
1270
|
+
if (raw["url"]) {
|
|
1271
|
+
return {
|
|
1272
|
+
transport: "http",
|
|
1273
|
+
url: raw["url"],
|
|
1274
|
+
headers: raw["headers"] ?? {}
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
return void 0;
|
|
1278
|
+
}
|
|
1279
|
+
var openclawAdapter = {
|
|
1280
|
+
id: "openclaw",
|
|
1281
|
+
name: "OpenClaw",
|
|
1282
|
+
configPath: () => GLOBAL_CONFIG_PATH2,
|
|
1283
|
+
isGlobal: true,
|
|
1284
|
+
toAgentFormat: toAgentFormat6,
|
|
1285
|
+
fromAgentFormat: fromAgentFormat6,
|
|
1286
|
+
async read() {
|
|
1287
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH2);
|
|
1288
|
+
return data["mcpServers"] ?? {};
|
|
1289
|
+
},
|
|
1290
|
+
async write(_projectDir, serverName, config) {
|
|
1291
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH2);
|
|
1292
|
+
const servers = data["mcpServers"] ?? {};
|
|
1293
|
+
if (serverName in servers) {
|
|
1294
|
+
throw new Error(
|
|
1295
|
+
`Conflict: "${serverName}" already exists in OpenClaw config`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const updated = {
|
|
1299
|
+
...data,
|
|
1300
|
+
mcpServers: { ...servers, [serverName]: toAgentFormat6(config) }
|
|
1301
|
+
};
|
|
1302
|
+
await writeJson5File(GLOBAL_CONFIG_PATH2, updated);
|
|
1303
|
+
},
|
|
1304
|
+
async remove(_projectDir, serverName) {
|
|
1305
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH2);
|
|
1306
|
+
const servers = data["mcpServers"] ?? {};
|
|
1307
|
+
const { [serverName]: _, ...rest } = servers;
|
|
1308
|
+
await writeJson5File(GLOBAL_CONFIG_PATH2, { ...data, mcpServers: rest });
|
|
1309
|
+
},
|
|
1310
|
+
async has(_projectDir, serverName) {
|
|
1311
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH2);
|
|
1312
|
+
const servers = data["mcpServers"] ?? {};
|
|
1313
|
+
return serverName in servers;
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1198
1317
|
// src/adapters/index.ts
|
|
1199
1318
|
var allAdapters = [
|
|
1200
1319
|
claudeCodeAdapter,
|
|
1201
|
-
|
|
1320
|
+
codexAdapter,
|
|
1202
1321
|
geminiCliAdapter,
|
|
1203
1322
|
opencodeAdapter,
|
|
1204
|
-
antigravityAdapter
|
|
1323
|
+
antigravityAdapter,
|
|
1324
|
+
openclawAdapter
|
|
1205
1325
|
];
|
|
1206
1326
|
function detectAgents(projectDir) {
|
|
1207
1327
|
return allAdapters.filter((adapter) => {
|
|
1208
1328
|
if (adapter.isGlobal) {
|
|
1209
|
-
return
|
|
1329
|
+
return existsSync7(adapter.configPath(projectDir));
|
|
1210
1330
|
}
|
|
1211
|
-
return
|
|
1331
|
+
return existsSync7(adapter.configPath(projectDir));
|
|
1212
1332
|
});
|
|
1213
1333
|
}
|
|
1214
1334
|
|