mcp-for-i 0.1.11 → 0.1.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP-for-i
2
2
 
3
- `mcp-for-i` is a local MCP server + control plane for IBM i workflows. It provides secure connection onboarding, keychain-backed credentials, runtime update controls, and IBM i tools for agent-driven operations.
3
+ `mcp-for-i` is a local MCP server + control plane for IBM i workflows. It provides secure connection onboarding, keychain-backed credentials, cross-platform runtime update controls, and IBM i tools for agent-driven operations.
4
4
 
5
5
  ## Install
6
6
 
@@ -17,15 +17,19 @@ mcp-for-i --help
17
17
  mcp-for-i-control help
18
18
  ```
19
19
 
20
+ Supported shells and terminals include PowerShell, `cmd`, Git Bash, Bash, Zsh, and WSL shells, across Windows, macOS, and Linux.
21
+
20
22
  ## Quick Start
21
23
 
22
- 1. Start the local control plane:
24
+ 1. Open the local control plane:
23
25
 
24
26
  ```bash
25
- mcp-for-i-control serve
27
+ mcp-for-i control
26
28
  ```
27
29
 
28
- 2. Open the UI at:
30
+ `mcp-for-i control` will start the control server in the background if needed, then open the UI. If a browser cannot be opened in the current environment, it prints the local URL instead.
31
+
32
+ 2. The default local UI URL is:
29
33
 
30
34
  ```text
31
35
  http://127.0.0.1:3980
@@ -33,25 +37,45 @@ http://127.0.0.1:3980
33
37
 
34
38
  3. Add IBM i connection profiles in the UI. Passwords are stored in the OS keychain, not plain config.
35
39
 
36
- ## Background Startup (Windows)
40
+ ## Control Commands
37
41
 
38
- Enable startup at login:
42
+ From an npm install or a git checkout build, these commands are the supported control entrypoints:
39
43
 
40
44
  ```bash
41
- mcp-for-i-control setup
45
+ mcp-for-i control
46
+ mcp-for-i control status
47
+ mcp-for-i control enable
48
+ mcp-for-i control disable
42
49
  ```
43
50
 
44
- Check status:
51
+ Equivalent direct control-plane binary commands:
45
52
 
46
53
  ```bash
54
+ mcp-for-i-control open
47
55
  mcp-for-i-control status
56
+ mcp-for-i-control enable
57
+ mcp-for-i-control disable
58
+ mcp-for-i-control serve
48
59
  ```
49
60
 
50
- Disable startup:
61
+ Behavior by environment:
62
+ - Windows: `enable` registers a Startup entry.
63
+ - macOS: `enable` registers a LaunchAgent.
64
+ - Linux: `enable` uses a user `systemd` service when available.
65
+ - WSL: `enable` installs a guarded `.bashrc` hook that starts the control plane on interactive Bash shell launch.
51
66
 
52
- ```bash
53
- mcp-for-i-control remove
54
- ```
67
+ ## Add To Coding Clients
68
+
69
+ The control-plane UI now includes a `Clients` tab with one-click setup for:
70
+ - `Codex Desktop / CLI`
71
+ - `Claude Code` when the `claude` CLI is installed
72
+ - `VS Code / GitHub Copilot` when the `code` CLI is installed
73
+
74
+ It also includes manual guidance for:
75
+ - `Project Bob`
76
+ - other MCP clients that use generic JSON/TOML stdio server configuration
77
+
78
+ The UI writes or invokes the local client-specific setup on the same machine where the control plane is running. If a client CLI is not installed, the UI falls back to showing the exact launch command/snippet to use manually.
55
79
 
56
80
  ## Update and Version Management
57
81
 
@@ -79,19 +103,18 @@ Check current installed version:
79
103
  mcp-for-i --version
80
104
  ```
81
105
 
82
- ## Control Plane Runtime Actions
106
+ ## Control Plane Updates
83
107
 
84
- In the UI, runtime buttons behave as follows:
108
+ The control plane intentionally stays narrow:
85
109
 
86
- - `Install/Repair MCP`
87
- - If running from a git checkout: runs local dependency repair/build.
88
- - If running as npm-installed package: runs global npm install/repair for latest package.
89
110
  - `Update MCP`
90
111
  - If running from a git checkout: pulls latest changes from `origin/<current-branch>`, then installs/builds.
91
- - If running as npm-installed package: upgrades global npm package to latest.
92
- - On Windows global installs, update runs in a detached updater and briefly restarts control plane to avoid file-lock (`EBUSY`) errors.
112
+ - If running as an npm-installed package: upgrades the global npm package to latest.
113
+ - On Windows global installs, update still runs in a detached updater to avoid file-lock (`EBUSY`) errors during self-update.
93
114
  - `Update Skills`
94
- - Pulls/clones the configured skills repository and branch into the local `skills` directory.
115
+ - Pulls or clones the configured skills repository and branch into the local `skills` directory.
116
+
117
+ For npm installs and manual upgrades, prefer standard npm commands such as `npm i -g mcp-for-i@latest`.
95
118
 
96
119
  ## Development (Git Checkout)
97
120
 
@@ -104,6 +127,14 @@ npm test
104
127
  npm run start:controlplane
105
128
  ```
106
129
 
130
+ You can also use the built artifacts directly after build:
131
+
132
+ ```bash
133
+ node dist/index.js
134
+ node dist/index.js control
135
+ node dist/controlplane/index.js open
136
+ ```
137
+
107
138
  ## Automated Release (No Manual Version Bump)
108
139
 
109
140
  Use the release script instead of raw `npm publish`.
@@ -0,0 +1,292 @@
1
+ import fs from "fs/promises";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { spawn } from "child_process";
5
+ const SERVER_NAME = "mcp-for-i";
6
+ export class ClientIntegrationService {
7
+ rootDir;
8
+ constructor(rootDir) {
9
+ this.rootDir = rootDir;
10
+ }
11
+ async listTargets() {
12
+ const launchSpec = this.getLaunchSpec();
13
+ const [hasCodex, hasClaude, hasCode] = await Promise.all([
14
+ commandExists("codex"),
15
+ commandExists("claude"),
16
+ commandExists("code")
17
+ ]);
18
+ const [codexConfigured, claudeConfigured, codeConfigured] = await Promise.all([
19
+ hasCodex ? this.isCodexConfigured() : false,
20
+ hasClaude ? this.isClaudeConfigured() : false,
21
+ hasCode ? this.isCodeConfigured() : false
22
+ ]);
23
+ return [
24
+ {
25
+ id: "codex",
26
+ label: "Codex Desktop / CLI",
27
+ description: "Adds mcp-for-i to Codex using the local Codex MCP command.",
28
+ available: hasCodex,
29
+ autoInstall: hasCodex,
30
+ configured: codexConfigured,
31
+ details: hasCodex ? undefined : "codex command not found on PATH."
32
+ },
33
+ {
34
+ id: "claude-code",
35
+ label: "Claude Code",
36
+ description: "Adds mcp-for-i to Claude Code at user scope.",
37
+ available: hasClaude,
38
+ autoInstall: hasClaude,
39
+ configured: claudeConfigured,
40
+ details: hasClaude ? undefined : "claude command not found on PATH."
41
+ },
42
+ {
43
+ id: "vscode-copilot",
44
+ label: "VS Code / GitHub Copilot",
45
+ description: "Adds mcp-for-i to the current VS Code user profile via the code CLI.",
46
+ available: hasCode,
47
+ autoInstall: hasCode,
48
+ configured: codeConfigured,
49
+ details: hasCode ? undefined : "code command not found on PATH."
50
+ },
51
+ {
52
+ id: "project-bob",
53
+ label: "Project Bob",
54
+ description: "Manual instructions only. No stable local Project Bob automation path is configured here.",
55
+ available: true,
56
+ autoInstall: false,
57
+ configured: false,
58
+ details: "Use manual instructions until a documented local config or CLI flow is available."
59
+ },
60
+ {
61
+ id: "manual",
62
+ label: "Other MCP Clients",
63
+ description: "Show ready-to-copy config snippets for clients not listed above.",
64
+ available: true,
65
+ autoInstall: false,
66
+ configured: false,
67
+ details: `Launch command: ${launchSpec.command} ${launchSpec.args.join(" ")}`
68
+ }
69
+ ];
70
+ }
71
+ async installTarget(id) {
72
+ switch (id) {
73
+ case "codex":
74
+ return this.installCodex();
75
+ case "claude-code":
76
+ return this.installClaudeCode();
77
+ case "vscode-copilot":
78
+ return this.installVsCode();
79
+ case "project-bob":
80
+ return this.manualOnly("project-bob", "Project Bob");
81
+ case "manual":
82
+ return this.manualOnly("manual", "Other MCP Clients");
83
+ default:
84
+ throw new Error(`Unknown client target: ${id}`);
85
+ }
86
+ }
87
+ async installCodex() {
88
+ if (!await commandExists("codex")) {
89
+ return {
90
+ ok: false,
91
+ id: "codex",
92
+ label: "Codex Desktop / CLI",
93
+ message: "codex command was not found. Use the manual instructions below."
94
+ };
95
+ }
96
+ if (await this.isCodexConfigured()) {
97
+ return {
98
+ ok: true,
99
+ id: "codex",
100
+ label: "Codex Desktop / CLI",
101
+ message: "mcp-for-i is already configured in Codex."
102
+ };
103
+ }
104
+ const spec = this.getLaunchSpec();
105
+ const result = await runCommand("codex", ["mcp", "add", SERVER_NAME, "--", spec.command, ...spec.args]);
106
+ if (!result.ok)
107
+ throw new Error(result.stderr || result.stdout || "Failed to add mcp-for-i to Codex.");
108
+ return {
109
+ ok: true,
110
+ id: "codex",
111
+ label: "Codex Desktop / CLI",
112
+ message: "Added mcp-for-i to Codex.",
113
+ output: compactOutput(result)
114
+ };
115
+ }
116
+ async installClaudeCode() {
117
+ if (!await commandExists("claude")) {
118
+ return {
119
+ ok: false,
120
+ id: "claude-code",
121
+ label: "Claude Code",
122
+ message: "claude command was not found. Use the manual instructions below."
123
+ };
124
+ }
125
+ if (await this.isClaudeConfigured()) {
126
+ return {
127
+ ok: true,
128
+ id: "claude-code",
129
+ label: "Claude Code",
130
+ message: "mcp-for-i is already configured in Claude Code."
131
+ };
132
+ }
133
+ const spec = this.getLaunchSpec();
134
+ const result = await runCommand("claude", ["mcp", "add", "--scope", "user", "--transport", "stdio", SERVER_NAME, "--", spec.command, ...spec.args]);
135
+ if (!result.ok)
136
+ throw new Error(result.stderr || result.stdout || "Failed to add mcp-for-i to Claude Code.");
137
+ return {
138
+ ok: true,
139
+ id: "claude-code",
140
+ label: "Claude Code",
141
+ message: "Added mcp-for-i to Claude Code.",
142
+ output: compactOutput(result)
143
+ };
144
+ }
145
+ async installVsCode() {
146
+ if (!await commandExists("code")) {
147
+ return {
148
+ ok: false,
149
+ id: "vscode-copilot",
150
+ label: "VS Code / GitHub Copilot",
151
+ message: "code command was not found. Use the manual instructions below."
152
+ };
153
+ }
154
+ const spec = this.getLaunchSpec();
155
+ const payload = JSON.stringify({
156
+ name: SERVER_NAME,
157
+ command: spec.command,
158
+ args: spec.args
159
+ });
160
+ const result = await runCommand("code", ["--add-mcp", payload]);
161
+ if (!result.ok)
162
+ throw new Error(result.stderr || result.stdout || "Failed to add mcp-for-i to VS Code.");
163
+ return {
164
+ ok: true,
165
+ id: "vscode-copilot",
166
+ label: "VS Code / GitHub Copilot",
167
+ message: "Added mcp-for-i to VS Code / GitHub Copilot.",
168
+ output: compactOutput(result)
169
+ };
170
+ }
171
+ async manualOnly(id, label) {
172
+ const spec = this.getLaunchSpec();
173
+ const genericJson = JSON.stringify({
174
+ mcpServers: {
175
+ [SERVER_NAME]: {
176
+ command: spec.command,
177
+ args: spec.args
178
+ }
179
+ }
180
+ }, null, 2);
181
+ const vscodeJson = JSON.stringify({
182
+ name: SERVER_NAME,
183
+ command: spec.command,
184
+ args: spec.args
185
+ }, null, 2);
186
+ const codexToml = [
187
+ `[mcp_servers.${quoteTomlKey(SERVER_NAME)}]`,
188
+ `command = ${quoteToml(spec.command)}`,
189
+ `args = [${spec.args.map(quoteToml).join(", ")}]`
190
+ ].join("\n");
191
+ const claudeCommand = `claude mcp add --scope user --transport stdio ${SERVER_NAME} -- ${spec.command} ${spec.args.join(" ")}`;
192
+ const codexCommand = `codex mcp add ${SERVER_NAME} -- ${spec.command} ${spec.args.join(" ")}`;
193
+ const vscodeCommand = `code --add-mcp ${JSON.stringify(vscodeJson)}`;
194
+ return {
195
+ ok: true,
196
+ id,
197
+ label,
198
+ message: "Manual setup instructions",
199
+ output: [
200
+ "Codex:",
201
+ codexCommand,
202
+ "",
203
+ codexToml,
204
+ "",
205
+ "Claude Code:",
206
+ claudeCommand,
207
+ "",
208
+ "VS Code / GitHub Copilot:",
209
+ vscodeCommand,
210
+ "",
211
+ vscodeJson,
212
+ "",
213
+ "Generic JSON clients:",
214
+ genericJson,
215
+ "",
216
+ "Project Bob:",
217
+ "Use the generic JSON command/args above unless Project Bob publishes a stable local MCP config or CLI flow."
218
+ ]
219
+ };
220
+ }
221
+ async isCodexConfigured() {
222
+ const result = await runCommand("codex", ["mcp", "get", SERVER_NAME]);
223
+ return result.ok;
224
+ }
225
+ async isClaudeConfigured() {
226
+ const result = await runCommand("claude", ["mcp", "get", SERVER_NAME]);
227
+ return result.ok;
228
+ }
229
+ async isCodeConfigured() {
230
+ const userConfigPath = getVsCodeUserMcpPath();
231
+ try {
232
+ const raw = await fs.readFile(userConfigPath, "utf8");
233
+ const parsed = JSON.parse(raw);
234
+ if (Array.isArray(parsed?.servers)) {
235
+ return parsed.servers.some((entry) => entry?.name === SERVER_NAME);
236
+ }
237
+ if (parsed?.servers && typeof parsed.servers === "object") {
238
+ return Object.prototype.hasOwnProperty.call(parsed.servers, SERVER_NAME);
239
+ }
240
+ return false;
241
+ }
242
+ catch {
243
+ return false;
244
+ }
245
+ }
246
+ getLaunchSpec() {
247
+ return {
248
+ command: process.execPath,
249
+ args: [path.join(this.rootDir, "dist", "index.js")]
250
+ };
251
+ }
252
+ }
253
+ function getVsCodeUserMcpPath() {
254
+ if (process.platform === "win32") {
255
+ return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Code", "User", "mcp.json");
256
+ }
257
+ if (process.platform === "darwin") {
258
+ return path.join(os.homedir(), "Library", "Application Support", "Code", "User", "mcp.json");
259
+ }
260
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Code", "User", "mcp.json");
261
+ }
262
+ async function commandExists(command) {
263
+ const result = await runCommand(command, ["--help"]);
264
+ return result.ok;
265
+ }
266
+ async function runCommand(command, args) {
267
+ return new Promise(resolve => {
268
+ const child = spawn(command, args, {
269
+ windowsHide: true,
270
+ stdio: ["ignore", "pipe", "pipe"]
271
+ });
272
+ const stdout = [];
273
+ const stderr = [];
274
+ child.stdout.on("data", chunk => stdout.push(String(chunk)));
275
+ child.stderr.on("data", chunk => stderr.push(String(chunk)));
276
+ child.on("error", () => resolve({ ok: false, stdout: "", stderr: "" }));
277
+ child.on("exit", code => resolve({
278
+ ok: code === 0,
279
+ stdout: stdout.join("").trim(),
280
+ stderr: stderr.join("").trim()
281
+ }));
282
+ });
283
+ }
284
+ function compactOutput(result) {
285
+ return [result.stdout, result.stderr].filter(Boolean).flatMap(value => value.split(/\r?\n/)).filter(Boolean);
286
+ }
287
+ function quoteToml(value) {
288
+ return JSON.stringify(value);
289
+ }
290
+ function quoteTomlKey(value) {
291
+ return JSON.stringify(value);
292
+ }
@@ -0,0 +1,109 @@
1
+ import { startControlPlaneServer } from "./server.js";
2
+ import { openUrl } from "./platform.js";
3
+ import { getControlPlaneUrl } from "./serverShared.js";
4
+ import { disableBackgroundService, enableBackgroundService, ensureControlPlaneRunning, getServiceStatus } from "./serviceManager.js";
5
+ export async function runControlCommand(args, options) {
6
+ const stderr = options.stderr || process.stderr;
7
+ const command = normalizeCommand(args, options.defaultCommand || "serve");
8
+ const scriptPath = options.scriptPath;
9
+ const nodePath = options.nodePath || process.execPath;
10
+ switch (command) {
11
+ case "serve":
12
+ await serve(stderr);
13
+ return;
14
+ case "status":
15
+ await printStatus(stderr, scriptPath, nodePath);
16
+ return;
17
+ case "open":
18
+ await openControlUi(stderr, scriptPath, nodePath);
19
+ return;
20
+ case "enable":
21
+ await enableService(stderr, scriptPath, nodePath);
22
+ return;
23
+ case "disable":
24
+ await disableService(stderr, scriptPath, nodePath);
25
+ return;
26
+ case "help":
27
+ default:
28
+ printHelp(stderr);
29
+ return;
30
+ }
31
+ }
32
+ async function serve(stderr) {
33
+ try {
34
+ const server = await startControlPlaneServer();
35
+ stderr.write(`mcp-for-i control plane running at http://${server.host}:${server.port}\n`);
36
+ const shutdown = async () => {
37
+ await server.close();
38
+ process.exit(0);
39
+ };
40
+ process.on("SIGINT", shutdown);
41
+ process.on("SIGTERM", shutdown);
42
+ }
43
+ catch (err) {
44
+ if (err?.code === "EADDRINUSE") {
45
+ const url = getControlPlaneUrl();
46
+ stderr.write(`mcp-for-i control plane already running at ${url}\n`);
47
+ process.exit(0);
48
+ }
49
+ throw err;
50
+ }
51
+ }
52
+ async function printStatus(stderr, scriptPath, nodePath) {
53
+ const url = getControlPlaneUrl();
54
+ const status = await getServiceStatus(scriptPath, nodePath);
55
+ stderr.write(`Control plane URL: ${url}\n`);
56
+ stderr.write(`Platform: ${status.label} (${status.arch})\n`);
57
+ stderr.write(`Running: ${status.running ? "yes" : "no"}\n`);
58
+ stderr.write(`Background service: ${status.enabled ? "enabled" : "disabled"} via ${status.managedBy}\n`);
59
+ if (status.target)
60
+ stderr.write(`Target: ${status.target}\n`);
61
+ if (status.detail)
62
+ stderr.write(`Detail: ${status.detail}\n`);
63
+ }
64
+ async function openControlUi(stderr, scriptPath, nodePath) {
65
+ const url = getControlPlaneUrl();
66
+ const result = await ensureControlPlaneRunning(scriptPath, nodePath);
67
+ if (result.started) {
68
+ stderr.write(`Started control plane at ${url}\n`);
69
+ }
70
+ try {
71
+ await openUrl(url);
72
+ stderr.write(`Opening ${url}\n`);
73
+ }
74
+ catch {
75
+ stderr.write(`Control plane is running at ${url}\n`);
76
+ }
77
+ }
78
+ async function enableService(stderr, scriptPath, nodePath) {
79
+ const status = await enableBackgroundService(scriptPath, nodePath);
80
+ await ensureControlPlaneRunning(scriptPath, nodePath);
81
+ stderr.write(`Enabled background service via ${status.managedBy}\n`);
82
+ if (status.target)
83
+ stderr.write(`Target: ${status.target}\n`);
84
+ if (status.detail)
85
+ stderr.write(`Detail: ${status.detail}\n`);
86
+ }
87
+ async function disableService(stderr, scriptPath, nodePath) {
88
+ const status = await disableBackgroundService(scriptPath, nodePath);
89
+ stderr.write(`Disabled background service (${status.managedBy})\n`);
90
+ if (status.target)
91
+ stderr.write(`Target: ${status.target}\n`);
92
+ }
93
+ function printHelp(stderr) {
94
+ stderr.write(`mcp-for-i-control commands:\n`);
95
+ stderr.write(` serve Start control plane server in the foreground\n`);
96
+ stderr.write(` open Start control plane in the background if needed, then open UI\n`);
97
+ stderr.write(` status Show running and background-service status\n`);
98
+ stderr.write(` enable Enable background service for this platform\n`);
99
+ stderr.write(` disable Disable background service for this platform\n`);
100
+ stderr.write(` help Show this help text\n`);
101
+ }
102
+ function normalizeCommand(args, fallback) {
103
+ const first = (args[0] || fallback).toLowerCase();
104
+ if (!first || first === "start")
105
+ return fallback;
106
+ if (first === "--help" || first === "-h")
107
+ return "help";
108
+ return first;
109
+ }
@@ -1,120 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { startControlPlaneServer } from "./server.js";
3
- import { spawn } from "child_process";
4
2
  import { fileURLToPath } from "url";
5
- import { AUTOSTART_TASK_NAME, configureWindowsAutostart, getControlPlaneUrl, getWindowsAutostartStatus, isControlPlaneRunning, removeWindowsAutostart } from "./autostart.js";
6
- const command = normalizeCommand(process.argv.slice(2));
7
- switch (command) {
8
- case "serve":
9
- await serve();
10
- break;
11
- case "setup":
12
- await setupAutostart();
13
- break;
14
- case "status":
15
- await printStatus();
16
- break;
17
- case "remove":
18
- case "uninstall":
19
- await removeAutostart();
20
- break;
21
- case "open":
22
- await openUi();
23
- break;
24
- case "help":
25
- default:
26
- printHelp();
27
- break;
28
- }
29
- async function serve() {
30
- try {
31
- const server = await startControlPlaneServer();
32
- process.stderr.write(`mcp-for-i control plane running at http://${server.host}:${server.port}\n`);
33
- const shutdown = async () => {
34
- await server.close();
35
- process.exit(0);
36
- };
37
- process.on("SIGINT", shutdown);
38
- process.on("SIGTERM", shutdown);
39
- }
40
- catch (err) {
41
- if (err?.code === "EADDRINUSE") {
42
- const url = getControlPlaneUrl();
43
- process.stderr.write(`mcp-for-i control plane already running at ${url}\n`);
44
- process.exit(0);
45
- }
46
- throw err;
47
- }
48
- }
49
- async function setupAutostart() {
50
- if (process.platform !== "win32") {
51
- process.stderr.write("Autostart setup is currently implemented for Windows only.\n");
52
- process.stderr.write(`Run manually with: mcp-for-i-control serve\n`);
53
- return;
54
- }
55
- const scriptPath = fileURLToPath(import.meta.url);
56
- await configureWindowsAutostart(scriptPath, process.execPath);
57
- process.stderr.write(`Autostart configured (${AUTOSTART_TASK_NAME}).\n`);
58
- process.stderr.write(`Control plane URL: ${getControlPlaneUrl()}\n`);
59
- }
60
- async function removeAutostart() {
61
- if (process.platform !== "win32") {
62
- process.stderr.write("Autostart removal is currently implemented for Windows only.\n");
63
- return;
64
- }
65
- try {
66
- await removeWindowsAutostart();
67
- process.stderr.write(`Autostart removed (${AUTOSTART_TASK_NAME}).\n`);
68
- }
69
- catch (err) {
70
- const msg = err?.message || String(err);
71
- if (msg.includes("cannot find the file specified") || msg.includes("ERROR: The system cannot find")) {
72
- process.stderr.write(`Autostart task not found (${AUTOSTART_TASK_NAME}).\n`);
73
- return;
74
- }
75
- throw err;
76
- }
77
- }
78
- async function printStatus() {
79
- const url = getControlPlaneUrl();
80
- const running = await isControlPlaneRunning(url);
81
- process.stderr.write(`Control plane URL: ${url}\n`);
82
- process.stderr.write(`Running: ${running ? "yes" : "no"}\n`);
83
- if (process.platform === "win32") {
84
- const auto = await getWindowsAutostartStatus();
85
- process.stderr.write(`Autostart task: ${auto.installed ? "installed" : "not installed"}\n`);
86
- if (auto.state) {
87
- process.stderr.write(`Autostart state: ${auto.state}\n`);
88
- }
89
- }
90
- else {
91
- process.stderr.write("Autostart task: platform not managed by this command\n");
92
- }
93
- }
94
- async function openUi() {
95
- const url = getControlPlaneUrl();
96
- if (process.platform === "win32") {
97
- spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
98
- }
99
- else if (process.platform === "darwin") {
100
- spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
101
- }
102
- else {
103
- spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
104
- }
105
- process.stderr.write(`Opening ${url}\n`);
106
- }
107
- function printHelp() {
108
- process.stderr.write(`mcp-for-i-control commands:\n`);
109
- process.stderr.write(` serve Start control plane server (foreground)\n`);
110
- process.stderr.write(` setup Configure autostart at login and launch now\n`);
111
- process.stderr.write(` status Show running/autostart status\n`);
112
- process.stderr.write(` remove Remove autostart configuration\n`);
113
- process.stderr.write(` open Open UI URL in default browser\n`);
114
- }
115
- function normalizeCommand(args) {
116
- const first = (args[0] || "serve").toLowerCase();
117
- if (!first || first === "start")
118
- return "serve";
119
- return first;
120
- }
3
+ import { runControlCommand } from "./commands.js";
4
+ await runControlCommand(process.argv.slice(2), {
5
+ defaultCommand: "serve",
6
+ scriptPath: fileURLToPath(import.meta.url)
7
+ });