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.
- package/README.md +17 -0
- package/dist/config/store.js +229 -109
- package/dist/controlplane/auth.js +128 -0
- package/dist/controlplane/clientIntegrationService.js +83 -4
- package/dist/controlplane/commands.js +14 -9
- package/dist/controlplane/connectionService.js +110 -5
- package/dist/controlplane/connectionTest.js +329 -0
- package/dist/controlplane/errors.js +12 -0
- package/dist/controlplane/runtimeService.js +98 -26
- package/dist/controlplane/server.js +122 -11
- package/dist/controlplane/serverShared.js +25 -6
- package/dist/controlplane/serviceManager.js +113 -100
- package/dist/controlplane/ui.js +1742 -920
- package/dist/ibmi/CompileTools.js +29 -1
- package/dist/ibmi/IBMiContent.js +435 -63
- package/dist/ibmi/IbmDocs.js +685 -0
- package/dist/ibmi/LocalLanguageActions.js +129 -3
- package/dist/ibmi/Search.js +158 -52
- package/dist/ibmi/Tools.js +7 -10
- package/dist/ibmi/client.js +220 -31
- package/dist/ibmi/evfevent.js +256 -0
- package/dist/ibmi/knownHosts.js +190 -0
- package/dist/ibmi/libraryList.js +93 -0
- package/dist/ibmi/sourceInspect.js +815 -0
- package/dist/ibmi/sqlJob.js +888 -0
- package/dist/ibmi/sqlParse.js +317 -0
- package/dist/ibmi/tn5250/certTrust.js +59 -0
- package/dist/ibmi/tn5250/client.js +310 -0
- package/dist/ibmi/tn5250/datastream.js +109 -0
- package/dist/ibmi/tn5250/ebcdic.js +74 -0
- package/dist/ibmi/tn5250/index.js +6 -0
- package/dist/ibmi/tn5250/screen.js +827 -0
- package/dist/ibmi/tn5250/telnet.js +186 -0
- package/dist/ibmi/variables.js +18 -7
- package/dist/index.js +43 -22
- package/dist/mcp/annotations.js +155 -0
- package/dist/mcp/audit.js +490 -133
- package/dist/mcp/confirm.js +48 -0
- package/dist/mcp/context.js +43 -5
- package/dist/mcp/deployMethods.js +192 -0
- package/dist/mcp/logger.js +5 -10
- package/dist/mcp/modules/bulkMembers.js +476 -0
- package/dist/mcp/modules/changedLines.js +189 -0
- package/dist/mcp/modules/compare.js +198 -0
- package/dist/mcp/modules/compile.js +515 -0
- package/dist/mcp/modules/ifsOps.js +123 -0
- package/dist/mcp/modules/index.js +38 -0
- package/dist/mcp/modules/inspect.js +811 -0
- package/dist/mcp/modules/libl.js +246 -0
- package/dist/mcp/modules/members.js +171 -0
- package/dist/mcp/modules/objects.js +339 -0
- package/dist/mcp/modules/system.js +266 -0
- package/dist/mcp/modules/tn5250.js +515 -0
- package/dist/mcp/policy.js +412 -0
- package/dist/mcp/tools.js +1700 -698
- package/dist/security/credentialStore.js +62 -13
- package/dist/security/localPaths.js +138 -0
- package/dist/security/redact.js +79 -0
- package/dist/util/atomicWrite.js +101 -0
- package/dist/util/fileLock.js +206 -0
- package/docs/capabilities.md +50 -7
- package/docs/configuration.md +189 -60
- package/docs/examples.md +109 -73
- package/docs/overview.md +15 -15
- package/docs/security.md +101 -16
- package/docs/tools.md +363 -188
- 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
|
-
|
|
186
|
-
|
|
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
|
|
218
|
-
const
|
|
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) {
|