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.
@@ -87,9 +87,9 @@ async function writeTomlFile(
87
87
  await writeFile(filePath, stringifyToml(data) + "\n", "utf-8");
88
88
  }
89
89
 
90
- export const codexCliAdapter: AgentAdapter = {
91
- id: "codex-cli",
92
- name: "Codex CLI",
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 CLI config`,
111
+ `Conflict: "${serverName}" already exists in Codex config`,
112
112
  );
113
113
  }
114
114
  const updated = {
@@ -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 { codexCliAdapter } from "./codex-cli.js";
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
- codexCliAdapter,
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 CLI** (.codex/config.toml)
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 CLI, Gemini CLI)
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-cli"
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;