mcp-for-i 0.1.12 → 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,190 +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, cross-platform 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
- Supported shells and terminals include PowerShell, `cmd`, Git Bash, Bash, Zsh, and WSL shells, across Windows, macOS, and Linux.
21
-
22
- ## Quick Start
23
-
24
- 1. Open the local control plane:
25
-
26
- ```bash
27
- mcp-for-i control
28
- ```
29
-
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:
33
-
34
- ```text
35
- http://127.0.0.1:3980
36
- ```
37
-
38
- 3. Add IBM i connection profiles in the UI. Passwords are stored in the OS keychain, not plain config.
39
-
40
- ## Control Commands
41
-
42
- From an npm install or a git checkout build, these commands are the supported control entrypoints:
43
-
44
- ```bash
45
- mcp-for-i control
46
- mcp-for-i control status
47
- mcp-for-i control enable
48
- mcp-for-i control disable
49
- ```
50
-
51
- Equivalent direct control-plane binary commands:
52
-
53
- ```bash
54
- mcp-for-i-control open
55
- mcp-for-i-control status
56
- mcp-for-i-control enable
57
- mcp-for-i-control disable
58
- mcp-for-i-control serve
59
- ```
60
-
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.
66
-
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.
79
-
80
- ## Update and Version Management
81
-
82
- Update to latest globally:
83
-
84
- ```bash
85
- npm i -g mcp-for-i@latest
86
- ```
87
-
88
- Install a specific version:
89
-
90
- ```bash
91
- npm i -g mcp-for-i@0.1.1
92
- ```
93
-
94
- Rollback example:
95
-
96
- ```bash
97
- npm i -g mcp-for-i@0.1.0
98
- ```
99
-
100
- Check current installed version:
101
-
102
- ```bash
103
- mcp-for-i --version
104
- ```
105
-
106
- ## Control Plane Updates
107
-
108
- The control plane intentionally stays narrow:
109
-
110
- - `Update MCP`
111
- - If running from a git checkout: pulls latest changes from `origin/<current-branch>`, then installs/builds.
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.
114
- - `Update Skills`
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`.
118
-
119
- ## Development (Git Checkout)
120
-
121
- For contributors working from source:
122
-
123
- ```bash
124
- npm install
125
- npm run build
126
- npm test
127
- npm run start:controlplane
128
- ```
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
-
138
- ## Automated Release (No Manual Version Bump)
139
-
140
- Use the release script instead of raw `npm publish`.
141
-
142
- Patch release:
143
-
144
- ```bash
145
- npm run release:patch
146
- ```
147
-
148
- Minor release:
149
-
150
- ```bash
151
- npm run release:minor
152
- ```
153
-
154
- Major release:
155
-
156
- ```bash
157
- npm run release:major
158
- ```
159
-
160
- What it does:
161
- - Verifies git working tree is clean.
162
- - Runs `npm test` (unless `--skip-tests` is passed).
163
- - Checks npm published version.
164
- - Auto-bumps version only when local version is not ahead.
165
- - Publishes to npm.
166
- - Pushes commit/tag to GitHub.
167
-
168
- Optional flags:
169
-
170
- ```bash
171
- npm run release:patch -- --skip-tests
172
- npm run release:patch -- --otp=123456
173
- npm run release:patch -- --no-push
174
- ```
175
-
176
- ## Security Model
177
-
178
- - Credential-bearing direct tool arguments are blocked for connection creation/update flows.
179
- - Passwords are stored in keychain when available.
180
- - Control plane is local-first (`127.0.0.1` by default).
181
- - Guarded policy profile is the default for operational safety.
182
- - Tool schemas are strict (`additionalProperties: false`) with runtime argument validation, including nested connection/action/filter/profile payloads.
183
- - QSYS object names and source settings are validated before command execution to reduce interpolation and injection risk.
184
-
185
- ## Links
186
-
187
- - Main repo: [ashishthomas2202/mcp-for-i](https://github.com/ashishthomas2202/mcp-for-i)
188
- - Skills repo: [ashishthomas2202/mcp-for-i-skills](https://github.com/ashishthomas2202/mcp-for-i-skills)
189
- - Docs: `docs/`
190
- - 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`
@@ -5,7 +5,7 @@ import path from "path";
5
5
  import { URL } from "url";
6
6
  import { fileURLToPath } from "url";
7
7
  import { ConfigStore, getConfigDir } from "../config/store.js";
8
- import { isKeychainAvailable } from "../security/credentialStore.js";
8
+ import { getCredentialStoreStatus } from "../security/credentialStore.js";
9
9
  import { ConnectionService } from "./connectionService.js";
10
10
  import { RuntimeService } from "./runtimeService.js";
11
11
  import { ClientIntegrationService } from "./clientIntegrationService.js";
@@ -48,7 +48,8 @@ async function routeRequest(req, res, services) {
48
48
  return;
49
49
  }
50
50
  if (method === "GET" && pathname === "/api/health") {
51
- sendJson(res, 200, { ok: true, keychainAvailable: await isKeychainAvailable() });
51
+ const credentialStore = await getCredentialStoreStatus();
52
+ sendJson(res, 200, { ok: true, keychainAvailable: credentialStore.keychainAvailable, credentialStore });
52
53
  return;
53
54
  }
54
55
  if (method === "GET" && pathname === "/api/settings") {
@@ -124,7 +124,23 @@ export function renderControlPlaneHtml() {
124
124
  }
125
125
  .badge.latest { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }
126
126
  .badge.update { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
127
- .badge.unknown { color: var(--muted); }
127
+ .badge.unknown { color: var(--muted); }
128
+ .notice {
129
+ border: 1px solid var(--line);
130
+ border-radius: 10px;
131
+ background: var(--surface-2);
132
+ padding: 10px;
133
+ margin: 10px 0;
134
+ color: var(--muted);
135
+ }
136
+ .notice.warning {
137
+ color: var(--warn);
138
+ border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
139
+ }
140
+ .notice.success {
141
+ color: var(--ok);
142
+ border-color: color-mix(in srgb, var(--ok) 40%, var(--line));
143
+ }
128
144
  .fields { display: grid; gap: 9px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
129
145
  .field label {
130
146
  display: block; margin-bottom: 5px;
@@ -239,6 +255,7 @@ export function renderControlPlaneHtml() {
239
255
  <span class="badge unknown" id="startupBadge">Checking</span>
240
256
  </div>
241
257
  </div>
258
+ <div id="credentialNotice" class="notice">Credential store: checking...</div>
242
259
  <div id="versionCheckedAt" class="hint"></div>
243
260
  <h3 style="margin: 12px 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);">Session Snapshot</h3>
244
261
  <table>
@@ -613,6 +630,16 @@ export function renderControlPlaneHtml() {
613
630
  ].filter(Boolean).join(" ");
614
631
  $("autostartInfo").textContent = detail;
615
632
  }
633
+ function renderCredentialStore(status) {
634
+ const el = $("credentialNotice");
635
+ if (!status) {
636
+ el.textContent = "Credential store: unavailable.";
637
+ el.className = "notice warning";
638
+ return;
639
+ }
640
+ el.textContent = "Credential store: " + status.message;
641
+ el.className = "notice " + (status.persistent ? "success" : "warning");
642
+ }
616
643
  function renderClients(targets) {
617
644
  const list = Array.isArray(targets) ? targets : [];
618
645
  $("clientSummary").textContent = list.length
@@ -665,6 +692,7 @@ export function renderControlPlaneHtml() {
665
692
  const res = await fetch("/api/health", { cache: "no-store" });
666
693
  if (!res.ok) return false;
667
694
  const data = await res.json();
695
+ renderCredentialStore(data?.credentialStore || null);
668
696
  return Boolean(data?.ok);
669
697
  } catch {
670
698
  return false;
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "crypto";
3
+ import fs from "fs";
3
4
  import { fileURLToPath } from "url";
4
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -12,6 +13,7 @@ import { appendAuditRecord } from "./mcp/audit.js";
12
13
  import { runControlCommand } from "./controlplane/commands.js";
13
14
  const argv = process.argv.slice(2);
14
15
  const firstArg = (argv[0] || "").toLowerCase();
16
+ const packageVersion = readPackageVersion();
15
17
  if (firstArg === "control") {
16
18
  await runControlCommand(argv.slice(1), {
17
19
  defaultCommand: "open",
@@ -19,11 +21,15 @@ if (firstArg === "control") {
19
21
  });
20
22
  process.exit(0);
21
23
  }
24
+ if (firstArg === "--version" || firstArg === "-v" || firstArg === "version") {
25
+ process.stdout.write(`${packageVersion}\n`);
26
+ process.exit(0);
27
+ }
22
28
  if (firstArg === "--help" || firstArg === "-h" || firstArg === "help") {
23
29
  printHelp();
24
30
  process.exit(0);
25
31
  }
26
- const server = new Server({ name: "mcp-for-i", version: "0.1.8" }, { capabilities: { tools: {} } });
32
+ const server = new Server({ name: "mcp-for-i", version: packageVersion }, { capabilities: { tools: {} } });
27
33
  const ctx = new McpContext();
28
34
  const tools = getTools();
29
35
  const toolMap = new Map(tools.map(tool => [tool.name, tool]));
@@ -132,4 +138,15 @@ function printHelp() {
132
138
  process.stderr.write(` mcp-for-i control status Show control plane and background-service status\n`);
133
139
  process.stderr.write(` mcp-for-i control enable Enable background service for this platform\n`);
134
140
  process.stderr.write(` mcp-for-i control disable Disable background service for this platform\n`);
141
+ process.stderr.write(` mcp-for-i --version Print package version\n`);
142
+ }
143
+ function readPackageVersion() {
144
+ try {
145
+ const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
146
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
147
+ return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
148
+ }
149
+ catch {
150
+ return "0.0.0";
151
+ }
135
152
  }
@@ -1,5 +1,7 @@
1
+ import os from "os";
1
2
  const SERVICE = "mcp-for-i";
2
3
  const sessionStore = new Map();
4
+ let lastKeychainError;
3
5
  let keytar;
4
6
  async function getKeytar() {
5
7
  if (keytar !== undefined)
@@ -13,30 +15,155 @@ async function getKeytar() {
13
15
  }
14
16
  return keytar;
15
17
  }
18
+ export function detectCredentialEnvironment(env = process.env, platform = process.platform, release = os.release()) {
19
+ const isWsl = isWslRuntime(env, platform, release);
20
+ const shell = String(env.SHELL || env.ComSpec || env.PSModulePath || "").toLowerCase();
21
+ const terminal = platform === "win32"
22
+ ? shell.includes("bash") || shell.includes("git") || env.MSYSTEM
23
+ ? "git-bash"
24
+ : env.PSModulePath
25
+ ? "powershell"
26
+ : "cmd"
27
+ : isWsl
28
+ ? "wsl"
29
+ : shell.includes("zsh")
30
+ ? "zsh"
31
+ : shell.includes("bash")
32
+ ? "bash"
33
+ : "unknown";
34
+ const nativeStore = platform === "win32"
35
+ ? "Windows Credential Manager"
36
+ : platform === "darwin"
37
+ ? "macOS Keychain"
38
+ : "Linux Secret Service";
39
+ return {
40
+ platform,
41
+ arch: process.arch,
42
+ isWsl,
43
+ terminal,
44
+ nativeStore,
45
+ label: isWsl
46
+ ? "Linux (WSL)"
47
+ : platform === "win32"
48
+ ? "Windows"
49
+ : platform === "darwin"
50
+ ? "macOS"
51
+ : platform === "linux"
52
+ ? "Linux"
53
+ : platform
54
+ };
55
+ }
56
+ function getCredentialStoreMode() {
57
+ const value = String(process.env.MCP_FOR_I_CREDENTIAL_STORE || "auto").trim().toLowerCase();
58
+ if (value === "keychain" || value === "session")
59
+ return value;
60
+ return "auto";
61
+ }
62
+ function describeError(err) {
63
+ return err instanceof Error ? err.message : String(err);
64
+ }
16
65
  export async function setPassword(connectionName, password) {
66
+ if (getCredentialStoreMode() === "session") {
67
+ sessionStore.set(connectionName, password);
68
+ return;
69
+ }
17
70
  const kt = await getKeytar();
18
71
  if (kt) {
19
- await kt.setPassword(SERVICE, connectionName, password);
20
- }
21
- else {
22
- sessionStore.set(connectionName, password);
72
+ try {
73
+ await kt.setPassword(SERVICE, connectionName, password);
74
+ lastKeychainError = undefined;
75
+ sessionStore.delete(connectionName);
76
+ return;
77
+ }
78
+ catch (err) {
79
+ lastKeychainError = describeError(err);
80
+ }
23
81
  }
82
+ sessionStore.set(connectionName, password);
24
83
  }
25
84
  export async function getPassword(connectionName) {
85
+ if (getCredentialStoreMode() === "session") {
86
+ return sessionStore.get(connectionName);
87
+ }
26
88
  const kt = await getKeytar();
27
89
  if (kt) {
28
- return kt.getPassword(SERVICE, connectionName);
90
+ try {
91
+ const password = await kt.getPassword(SERVICE, connectionName);
92
+ lastKeychainError = undefined;
93
+ if (password)
94
+ return password;
95
+ }
96
+ catch (err) {
97
+ lastKeychainError = describeError(err);
98
+ }
29
99
  }
30
100
  return sessionStore.get(connectionName);
31
101
  }
32
102
  export async function deletePassword(connectionName) {
103
+ if (getCredentialStoreMode() === "session") {
104
+ sessionStore.delete(connectionName);
105
+ return;
106
+ }
33
107
  const kt = await getKeytar();
34
108
  if (kt) {
35
- await kt.deletePassword(SERVICE, connectionName);
109
+ try {
110
+ await kt.deletePassword(SERVICE, connectionName);
111
+ lastKeychainError = undefined;
112
+ }
113
+ catch (err) {
114
+ lastKeychainError = describeError(err);
115
+ }
36
116
  }
37
117
  sessionStore.delete(connectionName);
38
118
  }
39
119
  export async function isKeychainAvailable() {
120
+ if (getCredentialStoreMode() === "session")
121
+ return false;
40
122
  const kt = await getKeytar();
41
- return Boolean(kt);
123
+ if (!kt)
124
+ return false;
125
+ try {
126
+ if (typeof kt.findCredentials === "function") {
127
+ await kt.findCredentials(SERVICE);
128
+ }
129
+ lastKeychainError = undefined;
130
+ return true;
131
+ }
132
+ catch (err) {
133
+ lastKeychainError = describeError(err);
134
+ return false;
135
+ }
136
+ }
137
+ export async function getCredentialStoreStatus() {
138
+ const requestedMode = getCredentialStoreMode();
139
+ const environment = detectCredentialEnvironment();
140
+ const keychainAvailable = await isKeychainAvailable();
141
+ const isWsl = environment.isWsl;
142
+ const mode = keychainAvailable ? "keychain" : "session";
143
+ return {
144
+ mode,
145
+ requestedMode,
146
+ persistent: keychainAvailable,
147
+ keychainAvailable,
148
+ platform: environment.platform,
149
+ terminal: environment.terminal,
150
+ nativeStore: environment.nativeStore,
151
+ runtimeLabel: environment.label,
152
+ isWsl,
153
+ error: lastKeychainError,
154
+ message: keychainAvailable
155
+ ? `Passwords are stored in ${environment.nativeStore}.`
156
+ : requestedMode === "session"
157
+ ? "Password storage is configured for session-only mode by MCP_FOR_I_CREDENTIAL_STORE=session."
158
+ : isWsl
159
+ ? "WSL could not access a persistent Linux Secret Service keychain; passwords are kept in this process only. Use SSH keys, install/configure libsecret in WSL, or run MCP-for-i from Windows to use Windows Credential Manager."
160
+ : "No persistent OS keychain is available; passwords are kept in this process only."
161
+ };
162
+ }
163
+ function isWslRuntime(env = process.env, platform = process.platform, release = os.release()) {
164
+ if (platform !== "linux")
165
+ return false;
166
+ if (env.WSL_DISTRO_NAME || env.WSL_INTEROP)
167
+ return true;
168
+ return /microsoft/i.test(release);
42
169
  }
@@ -20,6 +20,9 @@ Default settings (editable in the config file):
20
20
 
21
21
  ## Connections
22
22
  Connections are stored without passwords. Passwords are stored in OS keychain (or in-session fallback). Each connection can include optional `settings` overrides.
23
+ The control plane health response and Overview page report whether password storage is persistent (`keychain`) or process-only (`session`).
24
+ WSL users should note that Linux Secret Service is separate from Windows Credential Manager; if WSL has no working keychain, passwords must be re-entered after the process exits.
25
+ Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage even when a keychain is available.
23
26
  Connections can also define policy, for example:
24
27
  - `read-only`
25
28
  - `guarded` (default; approvals required for risky operations)
@@ -6,6 +6,10 @@
6
6
  - On Linux: `libsecret` (for keychain via `keytar`)
7
7
  - Works from PowerShell, `cmd`, Git Bash, Bash, Zsh, and WSL shells
8
8
 
9
+ WSL note: password persistence depends on a working Linux Secret Service inside the WSL distro. WSL does not automatically share Windows Credential Manager with Linux `keytar`. If the control plane reports session-only credential storage, use SSH keys, configure libsecret/Secret Service in WSL, or run MCP-for-i from Windows.
10
+
11
+ Credential storage follows the Node runtime, not only the visible terminal: PowerShell, `cmd`, and Git Bash with Windows Node use Windows Credential Manager; WSL with Linux Node uses Linux Secret Service.
12
+
9
13
  ## Install
10
14
  1. Clone or copy this repo for git-checkout usage, or install globally for npm usage
11
15
  2. For git-checkout usage, install dependencies:
package/docs/security.md CHANGED
@@ -1,7 +1,15 @@
1
1
  # Security
2
2
 
3
3
  - Credentials are stored in OS keychain via `keytar` (Windows Credential Manager, macOS Keychain, Linux libsecret).
4
- - If keychain is unavailable, credentials are stored in memory for the current session only.
4
+ - Credential storage is selected at runtime from the Node process environment:
5
+ - Windows Node from PowerShell, `cmd`, or Git Bash uses Windows Credential Manager.
6
+ - macOS Node uses macOS Keychain.
7
+ - Linux Node uses Linux Secret Service.
8
+ - WSL Node is Linux and uses the WSL distro's Linux Secret Service, not Windows Credential Manager.
9
+ - Keychain availability is checked as an actual read operation, not only as a module import.
10
+ - If keychain is unavailable or a keychain operation fails, credentials are stored in memory for the current process only.
11
+ - In WSL, `keytar` uses the Linux Secret Service inside the distro. It does not automatically use Windows Credential Manager. For persistent credentials in WSL, configure Linux libsecret/Secret Service, use SSH keys, or run MCP-for-i from Windows.
12
+ - Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage.
5
13
  - Passwords are never written to the config file.
6
14
  - `readOnlyMode` prevents write operations on members/IFS.
7
15
  - Connection policies support `read-only`, `guarded`, and `power-user` modes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-for-i",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "description": "Local MCP server and control plane for IBM i workflows",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "start:controlplane": "node dist/controlplane/index.js",
29
29
  "dev": "ts-node src/index.ts",
30
30
  "dev:controlplane": "ts-node src/controlplane/index.ts",
31
- "test": "npm run build && node --test test/tools.test.mjs test/variables.test.mjs test/validation.test.mjs test/config.migration.test.mjs test/sql.parse.test.mjs test/audit.chain.test.mjs test/controlplane.platform.test.mjs test/controlplane.service.test.mjs",
31
+ "test": "npm run build && node --test test/tools.test.mjs test/variables.test.mjs test/validation.test.mjs test/config.migration.test.mjs test/sql.parse.test.mjs test/audit.chain.test.mjs test/controlplane.platform.test.mjs test/controlplane.service.test.mjs test/credential.environment.test.mjs",
32
32
  "test:all": "npm run build && node --test test/*.mjs",
33
33
  "test:ibmi": "npm run build && node --test test/ibmi.integration.test.mjs",
34
34
  "test:mcp": "npm run build && node --test test/mcp.integration.test.mjs",