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
|
@@ -87,9 +87,9 @@ async function writeTomlFile(
|
|
|
87
87
|
await writeFile(filePath, stringifyToml(data) + "\n", "utf-8");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export const
|
|
91
|
-
id: "codex
|
|
92
|
-
name: "Codex
|
|
90
|
+
export const codexAdapter: AgentAdapter = {
|
|
91
|
+
id: "codex",
|
|
92
|
+
name: "Codex",
|
|
93
93
|
configPath: (projectDir) => join(projectDir, ".codex", "config.toml"),
|
|
94
94
|
isGlobal: false,
|
|
95
95
|
|
|
@@ -108,7 +108,7 @@ export const codexCliAdapter: AgentAdapter = {
|
|
|
108
108
|
const servers = (parsed["mcp_servers"] as Record<string, unknown>) ?? {};
|
|
109
109
|
if (serverName in servers) {
|
|
110
110
|
throw new Error(
|
|
111
|
-
`Conflict: "${serverName}" already exists in Codex
|
|
111
|
+
`Conflict: "${serverName}" already exists in Codex config`,
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
const updated = {
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import type { AgentAdapter, AgentId } from "../types.js";
|
|
3
3
|
import { claudeCodeAdapter } from "./claude-code.js";
|
|
4
|
-
import {
|
|
4
|
+
import { codexAdapter } from "./codex.js";
|
|
5
5
|
import { geminiCliAdapter } from "./gemini-cli.js";
|
|
6
6
|
import { opencodeAdapter } from "./opencode.js";
|
|
7
7
|
import { antigravityAdapter } from "./antigravity.js";
|
|
8
|
+
import { openclawAdapter } from "./openclaw.js";
|
|
8
9
|
|
|
9
10
|
export const allAdapters: readonly AgentAdapter[] = [
|
|
10
11
|
claudeCodeAdapter,
|
|
11
|
-
|
|
12
|
+
codexAdapter,
|
|
12
13
|
geminiCliAdapter,
|
|
13
14
|
opencodeAdapter,
|
|
14
15
|
antigravityAdapter,
|
|
16
|
+
openclawAdapter,
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
export function getAdapter(id: AgentId): AgentAdapter {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import JSON5 from "json5";
|
|
5
|
+
|
|
6
|
+
export async function readJson5File(
|
|
7
|
+
filePath: string,
|
|
8
|
+
): Promise<Record<string, unknown>> {
|
|
9
|
+
if (!existsSync(filePath)) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const raw = await readFile(filePath, "utf-8");
|
|
13
|
+
return JSON5.parse(raw) as Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function writeJson5File(
|
|
17
|
+
filePath: string,
|
|
18
|
+
data: Record<string, unknown>,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const dir = dirname(filePath);
|
|
21
|
+
if (!existsSync(dir)) {
|
|
22
|
+
await mkdir(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
25
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { AgentAdapter, DefaultConfig } from "../types.js";
|
|
4
|
+
import { readJson5File, writeJson5File } from "./json5-file.js";
|
|
5
|
+
import { buildEnvArgs, parseEnvArgs, resolveEnvInArgs } from "./env-args.js";
|
|
6
|
+
|
|
7
|
+
const GLOBAL_CONFIG_PATH = join(
|
|
8
|
+
homedir(),
|
|
9
|
+
".openclaw",
|
|
10
|
+
"openclaw.json",
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
function toAgentFormat(config: DefaultConfig): Record<string, unknown> {
|
|
14
|
+
if (config.transport === "stdio") {
|
|
15
|
+
const { resolvedArgs, remainingEnv } = resolveEnvInArgs(
|
|
16
|
+
config.args,
|
|
17
|
+
config.env,
|
|
18
|
+
);
|
|
19
|
+
const envArgs = buildEnvArgs(remainingEnv);
|
|
20
|
+
if (envArgs.length > 0) {
|
|
21
|
+
return {
|
|
22
|
+
command: "env",
|
|
23
|
+
args: [...envArgs, config.command, ...resolvedArgs],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
command: config.command,
|
|
28
|
+
args: resolvedArgs,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
url: config.url,
|
|
33
|
+
headers: { ...config.headers },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fromAgentFormat(
|
|
38
|
+
_name: string,
|
|
39
|
+
raw: Record<string, unknown>,
|
|
40
|
+
): DefaultConfig | undefined {
|
|
41
|
+
if (raw["command"]) {
|
|
42
|
+
const command = raw["command"] as string;
|
|
43
|
+
const rawArgs = (raw["args"] as string[]) ?? [];
|
|
44
|
+
const legacyEnv = raw["env"] as Record<string, string> | undefined;
|
|
45
|
+
|
|
46
|
+
if (legacyEnv && Object.keys(legacyEnv).length > 0) {
|
|
47
|
+
return { transport: "stdio", command, args: rawArgs, env: legacyEnv };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (command === "env") {
|
|
51
|
+
const { env, commandIndex } = parseEnvArgs(rawArgs);
|
|
52
|
+
return {
|
|
53
|
+
transport: "stdio",
|
|
54
|
+
command: rawArgs[commandIndex] ?? "",
|
|
55
|
+
args: rawArgs.slice(commandIndex + 1),
|
|
56
|
+
env,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { transport: "stdio", command, args: rawArgs, env: {} };
|
|
61
|
+
}
|
|
62
|
+
if (raw["url"]) {
|
|
63
|
+
return {
|
|
64
|
+
transport: "http",
|
|
65
|
+
url: raw["url"] as string,
|
|
66
|
+
headers: (raw["headers"] as Record<string, string>) ?? {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const openclawAdapter: AgentAdapter = {
|
|
73
|
+
id: "openclaw",
|
|
74
|
+
name: "OpenClaw",
|
|
75
|
+
configPath: () => GLOBAL_CONFIG_PATH,
|
|
76
|
+
isGlobal: true,
|
|
77
|
+
|
|
78
|
+
toAgentFormat,
|
|
79
|
+
fromAgentFormat,
|
|
80
|
+
|
|
81
|
+
async read() {
|
|
82
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH);
|
|
83
|
+
return (data["mcpServers"] as Record<string, unknown>) ?? {};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async write(_projectDir, serverName, config) {
|
|
87
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH);
|
|
88
|
+
const servers = (data["mcpServers"] as Record<string, unknown>) ?? {};
|
|
89
|
+
if (serverName in servers) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Conflict: "${serverName}" already exists in OpenClaw config`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const updated = {
|
|
95
|
+
...data,
|
|
96
|
+
mcpServers: { ...servers, [serverName]: toAgentFormat(config) },
|
|
97
|
+
};
|
|
98
|
+
await writeJson5File(GLOBAL_CONFIG_PATH, updated);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async remove(_projectDir, serverName) {
|
|
102
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH);
|
|
103
|
+
const servers = (data["mcpServers"] as Record<string, unknown>) ?? {};
|
|
104
|
+
const { [serverName]: _, ...rest } = servers;
|
|
105
|
+
await writeJson5File(GLOBAL_CONFIG_PATH, { ...data, mcpServers: rest });
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async has(_projectDir, serverName) {
|
|
109
|
+
const data = await readJson5File(GLOBAL_CONFIG_PATH);
|
|
110
|
+
const servers = (data["mcpServers"] as Record<string, unknown>) ?? {};
|
|
111
|
+
return serverName in servers;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
@@ -7,7 +7,7 @@ The 5 agents and their configuration differences:
|
|
|
7
7
|
- HTTP: { "type": "http", "url": "...", "headers": {...} }
|
|
8
8
|
- IMPORTANT: Do NOT use "env" field. Environment variables will be handled separately.
|
|
9
9
|
|
|
10
|
-
2. **Codex
|
|
10
|
+
2. **Codex** (.codex/config.toml)
|
|
11
11
|
- TOML format: command = "...", args = [...]
|
|
12
12
|
- Same key names as Claude Code but in TOML
|
|
13
13
|
- IMPORTANT: Do NOT use "env" field.
|
|
@@ -53,7 +53,7 @@ After analyzing the documentation, return a JSON object with this exact structur
|
|
|
53
53
|
|
|
54
54
|
Rules:
|
|
55
55
|
- "name" should be a kebab-case identifier for the server
|
|
56
|
-
- "default" should be the most common configuration (usually works for Claude Code, Codex
|
|
56
|
+
- "default" should be the most common configuration (usually works for Claude Code, Codex, Gemini CLI)
|
|
57
57
|
- Only add "overrides" for agents that need DIFFERENT configuration from the default
|
|
58
58
|
- OpenCode usually needs an override because its command format differs (array vs string+args)
|
|
59
59
|
- "requiredEnvVars" lists environment variable names the user needs to provide values for
|
package/src/types.ts
CHANGED
|
@@ -15,10 +15,11 @@ export type DefaultConfig = StdioConfig | HttpConfig;
|
|
|
15
15
|
|
|
16
16
|
export type AgentId =
|
|
17
17
|
| "claude-code"
|
|
18
|
-
| "codex
|
|
18
|
+
| "codex"
|
|
19
19
|
| "gemini-cli"
|
|
20
20
|
| "opencode"
|
|
21
|
-
| "antigravity"
|
|
21
|
+
| "antigravity"
|
|
22
|
+
| "openclaw";
|
|
22
23
|
|
|
23
24
|
export interface ServerDefinition {
|
|
24
25
|
readonly name: string;
|