mcp-for-i 0.1.11 → 0.1.13

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,159 +1,207 @@
1
- # MCP-for-i
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.
4
-
5
- ## Install
6
-
7
- Install globally from npm:
8
-
9
- ```bash
10
- npm i -g mcp-for-i
11
- ```
12
-
13
- Verify commands:
14
-
15
- ```bash
16
- mcp-for-i --help
17
- mcp-for-i-control help
18
- ```
19
-
20
- ## Quick Start
21
-
22
- 1. Start the local control plane:
23
-
24
- ```bash
25
- mcp-for-i-control serve
26
- ```
27
-
28
- 2. Open the UI at:
29
-
30
- ```text
31
- http://127.0.0.1:3980
32
- ```
33
-
34
- 3. Add IBM i connection profiles in the UI. Passwords are stored in the OS keychain, not plain config.
35
-
36
- ## Background Startup (Windows)
37
-
38
- Enable startup at login:
39
-
40
- ```bash
41
- mcp-for-i-control setup
42
- ```
43
-
44
- Check status:
45
-
46
- ```bash
47
- mcp-for-i-control status
48
- ```
49
-
50
- Disable startup:
51
-
52
- ```bash
53
- mcp-for-i-control remove
54
- ```
55
-
56
- ## Update and Version Management
57
-
58
- Update to latest globally:
59
-
60
- ```bash
61
- npm i -g mcp-for-i@latest
62
- ```
63
-
64
- Install a specific version:
65
-
66
- ```bash
67
- npm i -g mcp-for-i@0.1.1
68
- ```
69
-
70
- Rollback example:
71
-
72
- ```bash
73
- npm i -g mcp-for-i@0.1.0
74
- ```
75
-
76
- Check current installed version:
77
-
78
- ```bash
79
- mcp-for-i --version
80
- ```
81
-
82
- ## Control Plane Runtime Actions
83
-
84
- In the UI, runtime buttons behave as follows:
85
-
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
- - `Update MCP`
90
- - 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.
93
- - `Update Skills`
94
- - Pulls/clones the configured skills repository and branch into the local `skills` directory.
95
-
96
- ## Development (Git Checkout)
97
-
98
- For contributors working from source:
99
-
100
- ```bash
101
- npm install
102
- npm run build
103
- npm test
104
- npm run start:controlplane
105
- ```
106
-
107
- ## Automated Release (No Manual Version Bump)
108
-
109
- Use the release script instead of raw `npm publish`.
110
-
111
- Patch release:
112
-
113
- ```bash
114
- npm run release:patch
115
- ```
116
-
117
- Minor release:
118
-
119
- ```bash
120
- npm run release:minor
121
- ```
122
-
123
- Major release:
124
-
125
- ```bash
126
- npm run release:major
127
- ```
128
-
129
- What it does:
130
- - Verifies git working tree is clean.
131
- - Runs `npm test` (unless `--skip-tests` is passed).
132
- - Checks npm published version.
133
- - Auto-bumps version only when local version is not ahead.
134
- - Publishes to npm.
135
- - Pushes commit/tag to GitHub.
136
-
137
- Optional flags:
138
-
139
- ```bash
140
- npm run release:patch -- --skip-tests
141
- npm run release:patch -- --otp=123456
142
- npm run release:patch -- --no-push
143
- ```
144
-
145
- ## Security Model
146
-
147
- - Credential-bearing direct tool arguments are blocked for connection creation/update flows.
148
- - Passwords are stored in keychain when available.
149
- - Control plane is local-first (`127.0.0.1` by default).
150
- - Guarded policy profile is the default for operational safety.
151
- - Tool schemas are strict (`additionalProperties: false`) with runtime argument validation, including nested connection/action/filter/profile payloads.
152
- - QSYS object names and source settings are validated before command execution to reduce interpolation and injection risk.
153
-
154
- ## Links
155
-
156
- - Main repo: [ashishthomas2202/mcp-for-i](https://github.com/ashishthomas2202/mcp-for-i)
157
- - Skills repo: [ashishthomas2202/mcp-for-i-skills](https://github.com/ashishthomas2202/mcp-for-i-skills)
158
- - Docs: `docs/`
159
- - Full capability guide: `docs/capabilities.md`
1
+ # MCP-for-i
2
+
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
+
5
+ ## Install
6
+
7
+ The npm package name is `mcp-for-i`.
8
+
9
+ Install globally from npm:
10
+
11
+ ```bash
12
+ npm i -g mcp-for-i
13
+ ```
14
+
15
+ Verify commands:
16
+
17
+ ```bash
18
+ mcp-for-i --help
19
+ mcp-for-i-control help
20
+ ```
21
+
22
+ Supported shells and terminals include PowerShell, `cmd`, Git Bash, Bash, Zsh, and WSL shells, across Windows, macOS, and Linux.
23
+
24
+ ## Quick Start
25
+
26
+ 1. Open the local control plane:
27
+
28
+ ```bash
29
+ mcp-for-i control
30
+ ```
31
+
32
+ `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.
33
+
34
+ 2. The default local UI URL is:
35
+
36
+ ```text
37
+ http://127.0.0.1:3980
38
+ ```
39
+
40
+ 3. Add IBM i connection profiles in the UI. Passwords are stored in the OS keychain, not plain config.
41
+
42
+ WSL note: persistent password storage requires a working Linux Secret Service inside the WSL distro. WSL does not automatically use Windows Credential Manager; when no keychain is available, MCP-for-i keeps passwords only for the current process and the control plane shows a warning.
43
+
44
+ Credential storage adapts to the Node runtime: Windows Node from PowerShell, `cmd`, or Git Bash uses Windows Credential Manager; macOS uses Keychain; Linux uses Secret Service; WSL uses the WSL distro's Linux Secret Service.
45
+
46
+ ## Control Commands
47
+
48
+ From an npm install or a git checkout build, these commands are the supported control entrypoints:
49
+
50
+ ```bash
51
+ mcp-for-i control
52
+ mcp-for-i control status
53
+ mcp-for-i control enable
54
+ mcp-for-i control disable
55
+ ```
56
+
57
+ Equivalent direct control-plane binary commands:
58
+
59
+ ```bash
60
+ mcp-for-i-control open
61
+ mcp-for-i-control status
62
+ mcp-for-i-control enable
63
+ mcp-for-i-control disable
64
+ mcp-for-i-control serve
65
+ ```
66
+
67
+ Behavior by environment:
68
+ - Windows: `enable` registers a Startup entry.
69
+ - macOS: `enable` registers a LaunchAgent.
70
+ - Linux: `enable` uses a user `systemd` service when available.
71
+ - WSL: `enable` installs a guarded `.bashrc` hook that starts the control plane on interactive Bash shell launch.
72
+
73
+ ## Add To Coding Clients
74
+
75
+ The control-plane UI now includes a `Clients` tab with one-click setup for:
76
+ - `Codex Desktop / CLI`
77
+ - `Claude Code` when the `claude` CLI is installed
78
+ - `VS Code / GitHub Copilot` when the `code` CLI is installed
79
+
80
+ It also includes manual guidance for:
81
+ - `Project Bob`
82
+ - other MCP clients that use generic JSON/TOML stdio server configuration
83
+
84
+ 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.
85
+
86
+ ## Update and Version Management
87
+
88
+ Update to latest globally:
89
+
90
+ ```bash
91
+ npm i -g mcp-for-i@latest
92
+ ```
93
+
94
+ Install a specific version:
95
+
96
+ ```bash
97
+ npm i -g mcp-for-i@0.1.1
98
+ ```
99
+
100
+ Rollback example:
101
+
102
+ ```bash
103
+ npm i -g mcp-for-i@0.1.0
104
+ ```
105
+
106
+ Check current installed version:
107
+
108
+ ```bash
109
+ mcp-for-i --version
110
+ ```
111
+
112
+ ## Control Plane Updates
113
+
114
+ The control plane intentionally stays narrow:
115
+
116
+ - `Update MCP`
117
+ - If running from a git checkout: pulls latest changes from `origin/<current-branch>`, then installs/builds.
118
+ - If running as an npm-installed package: upgrades the global npm package to latest.
119
+ - On Windows global installs, update still runs in a detached updater to avoid file-lock (`EBUSY`) errors during self-update.
120
+ - `Update Skills`
121
+ - Pulls or clones the configured skills repository and branch into the local `skills` directory.
122
+
123
+ For npm installs and manual upgrades, prefer standard npm commands such as `npm i -g mcp-for-i@latest`.
124
+
125
+ ## Development (Git Checkout)
126
+
127
+ For contributors working from source:
128
+
129
+ ```bash
130
+ npm install
131
+ npm run build
132
+ npm test
133
+ npm run start:controlplane
134
+ ```
135
+
136
+ You can also use the built artifacts directly after build:
137
+
138
+ ```bash
139
+ node dist/index.js
140
+ node dist/index.js control
141
+ node dist/controlplane/index.js open
142
+ ```
143
+
144
+ ## Automated Release (No Manual Version Bump)
145
+
146
+ Use the release script instead of raw `npm publish`. The script publishes the `mcp-for-i` npm package.
147
+
148
+ Before releasing, verify npm auth and the currently published version:
149
+
150
+ ```bash
151
+ npm whoami
152
+ npm view mcp-for-i version
153
+ ```
154
+
155
+ If `npm whoami` fails, run `npm login` first.
156
+
157
+ Patch release:
158
+
159
+ ```bash
160
+ npm run release:patch
161
+ ```
162
+
163
+ Minor release:
164
+
165
+ ```bash
166
+ npm run release:minor
167
+ ```
168
+
169
+ Major release:
170
+
171
+ ```bash
172
+ npm run release:major
173
+ ```
174
+
175
+ What it does:
176
+ - Verifies git working tree is clean.
177
+ - Runs `npm test` (unless `--skip-tests` is passed).
178
+ - Checks npm published version.
179
+ - Auto-bumps version only when local version is not ahead.
180
+ - Publishes to npm.
181
+ - Pushes commit/tag to GitHub.
182
+
183
+ Optional flags:
184
+
185
+ ```bash
186
+ npm run release:patch -- --skip-tests
187
+ npm run release:patch -- --otp=123456
188
+ npm run release:patch -- --no-push
189
+ ```
190
+
191
+ OTP is only needed when the npm account has two-factor authentication enabled for publishing. If npm asks for a one-time password, use the current code from the authenticator app or recovery method configured on the npm account, then rerun with `--otp=<code>`. If the account does not have publish-time 2FA enabled, no OTP is needed.
192
+
193
+ ## Security Model
194
+
195
+ - Credential-bearing direct tool arguments are blocked for connection creation/update flows.
196
+ - Passwords are stored in keychain when available; failed or missing keychain access falls back to process-only session storage with a visible control-plane warning.
197
+ - Control plane is local-first (`127.0.0.1` by default).
198
+ - Guarded policy profile is the default for operational safety.
199
+ - Tool schemas are strict (`additionalProperties: false`) with runtime argument validation, including nested connection/action/filter/profile payloads.
200
+ - QSYS object names and source settings are validated before command execution to reduce interpolation and injection risk.
201
+
202
+ ## Links
203
+
204
+ - Main repo: [ashishthomas2202/mcp-for-i](https://github.com/ashishthomas2202/mcp-for-i)
205
+ - Skills repo: [ashishthomas2202/mcp-for-i-skills](https://github.com/ashishthomas2202/mcp-for-i-skills)
206
+ - Docs: `docs/`
207
+ - Full capability guide: `docs/capabilities.md`
@@ -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
+ }