mcp-for-i 0.1.6 → 0.1.8
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 +2 -0
- package/dist/config/store.js +4 -1
- package/dist/controlplane/connectionService.js +1 -1
- package/dist/controlplane/server.js +63 -2
- package/dist/controlplane/ui.js +645 -851
- package/dist/ibmi/CompileTools.js +25 -7
- package/dist/ibmi/IBMiContent.js +56 -34
- package/dist/ibmi/Search.js +9 -1
- package/dist/ibmi/client.js +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/context.js +270 -41
- package/dist/mcp/tools.js +624 -107
- package/docs/tools.md +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,8 @@ npm run release:patch -- --no-push
|
|
|
148
148
|
- Passwords are stored in keychain when available.
|
|
149
149
|
- Control plane is local-first (`127.0.0.1` by default).
|
|
150
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.
|
|
151
153
|
|
|
152
154
|
## Links
|
|
153
155
|
|
package/dist/config/store.js
CHANGED
|
@@ -17,7 +17,10 @@ const defaultSettings = {
|
|
|
17
17
|
objectFilters: [],
|
|
18
18
|
ifsShortcuts: [],
|
|
19
19
|
debugPort: 8005,
|
|
20
|
-
debugSepPort: 8008
|
|
20
|
+
debugSepPort: 8008,
|
|
21
|
+
sessionIdleMinutes: 30,
|
|
22
|
+
sessionPingSeconds: 15,
|
|
23
|
+
sessionReconnectAttempts: 2
|
|
21
24
|
};
|
|
22
25
|
export class ConfigStore {
|
|
23
26
|
configDir;
|
|
@@ -35,7 +35,7 @@ export class ConnectionService {
|
|
|
35
35
|
port: input.port ?? existing.port,
|
|
36
36
|
username: input.username ?? existing.username,
|
|
37
37
|
privateKeyPath: input.privateKeyPath ?? existing.privateKeyPath,
|
|
38
|
-
settings: input.settings
|
|
38
|
+
settings: input.settings ? { ...(existing.settings || {}), ...input.settings } : existing.settings,
|
|
39
39
|
policy: input.policy ?? existing.policy
|
|
40
40
|
};
|
|
41
41
|
await this.store.upsertConnection(next);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import http from "http";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import fsp from "fs/promises";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { URL } from "url";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { ConfigStore } from "../config/store.js";
|
|
7
|
+
import { ConfigStore, getConfigDir } from "../config/store.js";
|
|
7
8
|
import { isKeychainAvailable } from "../security/credentialStore.js";
|
|
8
9
|
import { ConnectionService } from "./connectionService.js";
|
|
9
10
|
import { RuntimeService } from "./runtimeService.js";
|
|
@@ -17,7 +18,7 @@ export async function startControlPlaneServer(opts) {
|
|
|
17
18
|
const runtime = new RuntimeService(rootDir);
|
|
18
19
|
const server = http.createServer(async (req, res) => {
|
|
19
20
|
try {
|
|
20
|
-
await routeRequest(req, res, { connections, runtime });
|
|
21
|
+
await routeRequest(req, res, { store, connections, runtime });
|
|
21
22
|
}
|
|
22
23
|
catch (err) {
|
|
23
24
|
sendJson(res, 500, { error: err?.message || String(err) });
|
|
@@ -48,6 +49,42 @@ async function routeRequest(req, res, services) {
|
|
|
48
49
|
sendJson(res, 200, { ok: true, keychainAvailable: await isKeychainAvailable() });
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
52
|
+
if (method === "GET" && pathname === "/api/settings") {
|
|
53
|
+
const settings = await services.store.getSettings();
|
|
54
|
+
sendJson(res, 200, { settings });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (method === "PUT" && pathname === "/api/settings") {
|
|
58
|
+
const body = await readJson(req);
|
|
59
|
+
const update = {};
|
|
60
|
+
try {
|
|
61
|
+
if (typeof body.sessionIdleMinutes !== "undefined") {
|
|
62
|
+
update.sessionIdleMinutes = parseNumberSetting(body.sessionIdleMinutes, 1, 24 * 60, "sessionIdleMinutes");
|
|
63
|
+
}
|
|
64
|
+
if (typeof body.sessionPingSeconds !== "undefined") {
|
|
65
|
+
update.sessionPingSeconds = parseNumberSetting(body.sessionPingSeconds, 5, 300, "sessionPingSeconds");
|
|
66
|
+
}
|
|
67
|
+
if (typeof body.sessionReconnectAttempts !== "undefined") {
|
|
68
|
+
update.sessionReconnectAttempts = parseNumberSetting(body.sessionReconnectAttempts, 1, 5, "sessionReconnectAttempts");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
sendJson(res, 400, { error: err?.message || String(err) });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(update).length === 0) {
|
|
76
|
+
sendJson(res, 400, { error: "No supported settings provided" });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await services.store.updateSettings(update);
|
|
80
|
+
const settings = await services.store.getSettings();
|
|
81
|
+
sendJson(res, 200, { settings });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (method === "GET" && pathname === "/api/sessions") {
|
|
85
|
+
sendJson(res, 200, { snapshot: await readSessionSnapshot() });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
51
88
|
if (method === "GET" && pathname === "/api/runtime/status") {
|
|
52
89
|
sendJson(res, 200, { jobs: services.runtime.getStatus() });
|
|
53
90
|
return;
|
|
@@ -197,3 +234,27 @@ function getDefaultRootDir() {
|
|
|
197
234
|
}
|
|
198
235
|
return process.cwd();
|
|
199
236
|
}
|
|
237
|
+
function parseNumberSetting(input, min, max, key) {
|
|
238
|
+
const value = Number(input);
|
|
239
|
+
if (!Number.isFinite(value)) {
|
|
240
|
+
throw new Error(`${key} must be a number`);
|
|
241
|
+
}
|
|
242
|
+
const normalized = Math.floor(value);
|
|
243
|
+
if (normalized < min || normalized > max) {
|
|
244
|
+
throw new Error(`${key} must be between ${min} and ${max}`);
|
|
245
|
+
}
|
|
246
|
+
return normalized;
|
|
247
|
+
}
|
|
248
|
+
async function readSessionSnapshot() {
|
|
249
|
+
const snapshotPath = path.join(getConfigDir(), "session-state.json");
|
|
250
|
+
try {
|
|
251
|
+
const raw = await fsp.readFile(snapshotPath, "utf8");
|
|
252
|
+
const parsed = JSON.parse(raw);
|
|
253
|
+
if (!parsed || typeof parsed !== "object")
|
|
254
|
+
return null;
|
|
255
|
+
return parsed;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|