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 +207 -190
- package/dist/controlplane/server.js +3 -2
- package/dist/controlplane/ui.js +29 -1
- package/dist/index.js +18 -1
- package/dist/security/credentialStore.js +134 -7
- package/docs/configuration.md +3 -0
- package/docs/installation.md +4 -0
- package/docs/security.md +9 -1
- package/package.json +2 -2
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
mcp-for-i
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- `
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
npm i -g mcp-for-i@
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npm i -g mcp-for-i@0.1.
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
mcp-for-i
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
npm
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
npm run release:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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 {
|
|
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
|
-
|
|
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") {
|
package/dist/controlplane/ui.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/docs/configuration.md
CHANGED
|
@@ -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)
|
package/docs/installation.md
CHANGED
|
@@ -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
|
-
-
|
|
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.
|
|
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",
|