mcp-for-i 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +17 -0
  2. package/dist/config/store.js +229 -109
  3. package/dist/controlplane/auth.js +128 -0
  4. package/dist/controlplane/clientIntegrationService.js +83 -4
  5. package/dist/controlplane/commands.js +14 -9
  6. package/dist/controlplane/connectionService.js +110 -5
  7. package/dist/controlplane/connectionTest.js +329 -0
  8. package/dist/controlplane/errors.js +12 -0
  9. package/dist/controlplane/runtimeService.js +98 -26
  10. package/dist/controlplane/server.js +122 -11
  11. package/dist/controlplane/serverShared.js +25 -6
  12. package/dist/controlplane/serviceManager.js +113 -100
  13. package/dist/controlplane/ui.js +1742 -920
  14. package/dist/ibmi/CompileTools.js +29 -1
  15. package/dist/ibmi/IBMiContent.js +435 -63
  16. package/dist/ibmi/IbmDocs.js +685 -0
  17. package/dist/ibmi/LocalLanguageActions.js +129 -3
  18. package/dist/ibmi/Search.js +158 -52
  19. package/dist/ibmi/Tools.js +7 -10
  20. package/dist/ibmi/client.js +220 -31
  21. package/dist/ibmi/evfevent.js +256 -0
  22. package/dist/ibmi/knownHosts.js +190 -0
  23. package/dist/ibmi/libraryList.js +93 -0
  24. package/dist/ibmi/sourceInspect.js +815 -0
  25. package/dist/ibmi/sqlJob.js +888 -0
  26. package/dist/ibmi/sqlParse.js +317 -0
  27. package/dist/ibmi/tn5250/certTrust.js +59 -0
  28. package/dist/ibmi/tn5250/client.js +310 -0
  29. package/dist/ibmi/tn5250/datastream.js +109 -0
  30. package/dist/ibmi/tn5250/ebcdic.js +74 -0
  31. package/dist/ibmi/tn5250/index.js +6 -0
  32. package/dist/ibmi/tn5250/screen.js +827 -0
  33. package/dist/ibmi/tn5250/telnet.js +186 -0
  34. package/dist/ibmi/variables.js +18 -7
  35. package/dist/index.js +43 -22
  36. package/dist/mcp/annotations.js +155 -0
  37. package/dist/mcp/audit.js +490 -133
  38. package/dist/mcp/confirm.js +48 -0
  39. package/dist/mcp/context.js +43 -5
  40. package/dist/mcp/deployMethods.js +192 -0
  41. package/dist/mcp/logger.js +5 -10
  42. package/dist/mcp/modules/bulkMembers.js +476 -0
  43. package/dist/mcp/modules/changedLines.js +189 -0
  44. package/dist/mcp/modules/compare.js +198 -0
  45. package/dist/mcp/modules/compile.js +515 -0
  46. package/dist/mcp/modules/ifsOps.js +123 -0
  47. package/dist/mcp/modules/index.js +38 -0
  48. package/dist/mcp/modules/inspect.js +811 -0
  49. package/dist/mcp/modules/libl.js +246 -0
  50. package/dist/mcp/modules/members.js +171 -0
  51. package/dist/mcp/modules/objects.js +339 -0
  52. package/dist/mcp/modules/system.js +266 -0
  53. package/dist/mcp/modules/tn5250.js +515 -0
  54. package/dist/mcp/policy.js +412 -0
  55. package/dist/mcp/tools.js +1700 -698
  56. package/dist/security/credentialStore.js +62 -13
  57. package/dist/security/localPaths.js +138 -0
  58. package/dist/security/redact.js +79 -0
  59. package/dist/util/atomicWrite.js +101 -0
  60. package/dist/util/fileLock.js +206 -0
  61. package/docs/capabilities.md +50 -7
  62. package/docs/configuration.md +189 -60
  63. package/docs/examples.md +109 -73
  64. package/docs/overview.md +15 -15
  65. package/docs/security.md +101 -16
  66. package/docs/tools.md +363 -188
  67. package/package.json +56 -53
@@ -4,7 +4,7 @@ import path from "path";
4
4
  import { spawn } from "child_process";
5
5
  import { getConfigDir } from "../config/store.js";
6
6
  import { getPlatformDescriptor } from "./platform.js";
7
- import { isControlPlaneRunning } from "./serverShared.js";
7
+ import { getControlPlanePort, isControlPlaneRunning } from "./serverShared.js";
8
8
  const SERVICE_ID = "mcp-for-i-controlplane";
9
9
  const MAC_LABEL = "com.mcpfori.controlplane";
10
10
  const RC_BEGIN = "# >>> mcp-for-i control >>>";
@@ -121,6 +121,7 @@ async function enableWindowsStartup(scriptPath, nodePath) {
121
121
  const startupScript = [
122
122
  "@echo off",
123
123
  "setlocal",
124
+ `set "MCP_FOR_I_CONTROL_PORT=${getControlPlanePort()}"`,
124
125
  `start \"\" /MIN ${command}`,
125
126
  "exit /b 0"
126
127
  ].join("\r\n");
@@ -133,28 +134,28 @@ async function enableMacLaunchAgent(scriptPath, nodePath) {
133
134
  const errPath = path.join(configDir, "controlplane.err.log");
134
135
  await fs.mkdir(path.dirname(agentPath), { recursive: true });
135
136
  await fs.mkdir(configDir, { recursive: true });
136
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
137
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
138
- <plist version="1.0">
139
- <dict>
140
- <key>Label</key>
141
- <string>${MAC_LABEL}</string>
142
- <key>ProgramArguments</key>
143
- <array>
144
- <string>${escapeXml(nodePath)}</string>
145
- <string>${escapeXml(scriptPath)}</string>
146
- <string>serve</string>
147
- </array>
148
- <key>RunAtLoad</key>
149
- <true/>
150
- <key>KeepAlive</key>
151
- <true/>
152
- <key>StandardOutPath</key>
153
- <string>${escapeXml(outPath)}</string>
154
- <key>StandardErrorPath</key>
155
- <string>${escapeXml(errPath)}</string>
156
- </dict>
157
- </plist>
137
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
138
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
139
+ <plist version="1.0">
140
+ <dict>
141
+ <key>Label</key>
142
+ <string>${MAC_LABEL}</string>
143
+ <key>ProgramArguments</key>
144
+ <array>
145
+ <string>${escapeXml(nodePath)}</string>
146
+ <string>${escapeXml(scriptPath)}</string>
147
+ <string>serve</string>
148
+ </array>
149
+ <key>RunAtLoad</key>
150
+ <true/>
151
+ <key>KeepAlive</key>
152
+ <true/>
153
+ <key>StandardOutPath</key>
154
+ <string>${escapeXml(outPath)}</string>
155
+ <key>StandardErrorPath</key>
156
+ <string>${escapeXml(errPath)}</string>
157
+ </dict>
158
+ </plist>
158
159
  `;
159
160
  await fs.writeFile(agentPath, plist, "utf8");
160
161
  await runQuiet("launchctl", ["unload", agentPath]);
@@ -170,20 +171,21 @@ async function enableLinuxSystemdService(scriptPath, nodePath) {
170
171
  const configDir = getConfigDir();
171
172
  await fs.mkdir(path.dirname(unitPath), { recursive: true });
172
173
  await fs.mkdir(configDir, { recursive: true });
173
- const unit = `[Unit]
174
- Description=MCP-for-i control plane
175
- After=network.target
176
-
177
- [Service]
178
- Type=simple
179
- ExecStart=${systemdQuote(nodePath)} ${systemdQuote(scriptPath)} serve
180
- Restart=on-failure
181
- RestartSec=3
182
- WorkingDirectory=${systemdQuote(path.dirname(scriptPath))}
183
- Environment=MCP_FOR_I_CONTROL_HOST=127.0.0.1
184
-
185
- [Install]
186
- WantedBy=default.target
174
+ const unit = `[Unit]
175
+ Description=MCP-for-i control plane
176
+ After=network.target
177
+
178
+ [Service]
179
+ Type=simple
180
+ ExecStart=${systemdQuote(nodePath)} ${systemdQuote(scriptPath)} serve
181
+ Restart=on-failure
182
+ RestartSec=3
183
+ WorkingDirectory=${systemdQuote(path.dirname(scriptPath))}
184
+ Environment=MCP_FOR_I_CONTROL_HOST=127.0.0.1
185
+ Environment=MCP_FOR_I_CONTROL_PORT=${getControlPlanePort()}
186
+
187
+ [Install]
188
+ WantedBy=default.target
187
189
  `;
188
190
  await fs.writeFile(unitPath, unit, "utf8");
189
191
  await runOrThrow("systemctl", ["--user", "daemon-reload"]);
@@ -214,73 +216,84 @@ async function disableBashRcHook() {
214
216
  }
215
217
  }
216
218
  async function writeBashLauncher(launcherPath, scriptPath, nodePath) {
217
- const configDir = getConfigDir();
218
- const logFile = path.join(configDir, "controlplane.log");
219
- const pythonHealthUrl = JSON.stringify("http://127.0.0.1:3980/api/health");
220
- const script = `#!/usr/bin/env bash
221
- set -euo pipefail
222
-
223
- URL="http://127.0.0.1:3980/api/health"
224
- LOG_FILE=${shellQuote(logFile)}
225
- LOCK_DIR="\${XDG_RUNTIME_DIR:-/tmp}/${SERVICE_ID}.lock"
226
-
227
- mkdir -p "$(dirname "$LOG_FILE")"
228
-
229
- health_check() {
230
- if command -v curl >/dev/null 2>&1; then
231
- curl -fsS --max-time 1 "$URL" >/dev/null 2>&1
232
- return $?
233
- fi
234
-
235
- if command -v wget >/dev/null 2>&1; then
236
- wget -q -T 1 -O - "$URL" >/dev/null 2>&1
237
- return $?
238
- fi
239
-
240
- if command -v python3 >/dev/null 2>&1; then
241
- python3 - <<'PY' >/dev/null 2>&1
242
- import sys, urllib.request
243
- try:
244
- with urllib.request.urlopen(${pythonHealthUrl}, timeout=1) as response:
245
- sys.exit(0 if response.status == 200 else 1)
246
- except Exception:
247
- sys.exit(1)
248
- PY
249
- return $?
250
- fi
251
-
252
- return 1
253
- }
254
-
255
- if health_check; then
256
- exit 0
257
- fi
258
-
259
- if mkdir "$LOCK_DIR" 2>/dev/null; then
260
- trap 'rmdir "$LOCK_DIR" >/dev/null 2>&1 || true' EXIT
261
- else
262
- exit 0
263
- fi
264
-
265
- if health_check; then
266
- exit 0
267
- fi
268
-
269
- nohup ${shellQuote(nodePath)} ${shellQuote(scriptPath)} serve >>"$LOG_FILE" 2>&1 </dev/null &
270
- `;
219
+ const logFile = path.join(getConfigDir(), "controlplane.log");
220
+ const script = buildBashLauncherScript({ scriptPath, nodePath, logFile, port: getControlPlanePort() });
271
221
  await fs.writeFile(launcherPath, script, { encoding: "utf8", mode: 0o755 });
272
222
  }
223
+ /**
224
+ * Builds the Bash launcher. The port defaults to the one configured when the
225
+ * service was enabled and can be overridden with MCP_FOR_I_CONTROL_PORT.
226
+ */
227
+ export function buildBashLauncherScript(opts) {
228
+ const { scriptPath, nodePath, logFile } = opts;
229
+ const port = Number.isInteger(opts.port) && opts.port > 0 && opts.port <= 65535 ? opts.port : 3980;
230
+ return `#!/usr/bin/env bash
231
+ set -euo pipefail
232
+
233
+ PORT="\${MCP_FOR_I_CONTROL_PORT:-${port}}"
234
+ case "$PORT" in
235
+ ""|*[!0-9]*) PORT=${port} ;;
236
+ esac
237
+ URL="http://127.0.0.1:\${PORT}/api/health"
238
+ LOG_FILE=${shellQuote(logFile)}
239
+ LOCK_DIR="\${XDG_RUNTIME_DIR:-/tmp}/${SERVICE_ID}.lock"
240
+
241
+ mkdir -p "$(dirname "$LOG_FILE")"
242
+
243
+ health_check() {
244
+ if command -v curl >/dev/null 2>&1; then
245
+ curl -fsS --max-time 1 "$URL" >/dev/null 2>&1
246
+ return $?
247
+ fi
248
+
249
+ if command -v wget >/dev/null 2>&1; then
250
+ wget -q -T 1 -O - "$URL" >/dev/null 2>&1
251
+ return $?
252
+ fi
253
+
254
+ if command -v python3 >/dev/null 2>&1; then
255
+ HEALTH_URL="$URL" python3 - <<'PY' >/dev/null 2>&1
256
+ import os, sys, urllib.request
257
+ try:
258
+ with urllib.request.urlopen(os.environ["HEALTH_URL"], timeout=1) as response:
259
+ sys.exit(0 if response.status == 200 else 1)
260
+ except Exception:
261
+ sys.exit(1)
262
+ PY
263
+ return $?
264
+ fi
265
+
266
+ return 1
267
+ }
268
+
269
+ if health_check; then
270
+ exit 0
271
+ fi
272
+
273
+ if mkdir "$LOCK_DIR" 2>/dev/null; then
274
+ trap 'rmdir "$LOCK_DIR" >/dev/null 2>&1 || true' EXIT
275
+ else
276
+ exit 0
277
+ fi
278
+
279
+ if health_check; then
280
+ exit 0
281
+ fi
282
+
283
+ MCP_FOR_I_CONTROL_PORT="$PORT" nohup ${shellQuote(nodePath)} ${shellQuote(scriptPath)} serve >>"$LOG_FILE" 2>&1 </dev/null &
284
+ `;
285
+ }
273
286
  function createBashRcBlock(launcherPath, wslOnly) {
274
287
  const guard = wslOnly ? 'if [ -n "${WSL_DISTRO_NAME:-}" ]' : "if true";
275
- return `${RC_BEGIN}
276
- case $- in
277
- *i*) ;;
278
- *) return ;;
279
- esac
280
-
281
- ${guard} && [ -x ${shellQuote(launcherPath)} ]; then
282
- ${shellQuote(launcherPath)} >/dev/null 2>&1 &
283
- fi
288
+ return `${RC_BEGIN}
289
+ case $- in
290
+ *i*) ;;
291
+ *) return ;;
292
+ esac
293
+
294
+ ${guard} && [ -x ${shellQuote(launcherPath)} ]; then
295
+ ${shellQuote(launcherPath)} >/dev/null 2>&1 &
296
+ fi
284
297
  ${RC_END}`;
285
298
  }
286
299
  async function hasManagedRcBlock(rcPath) {