mcp-for-i 0.1.2 → 0.1.4
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/dist/controlplane/runtimeService.js +3 -4
- package/dist/ibmi/client.js +28 -7
- package/dist/index.js +1 -1
- package/dist/mcp/tools.js +56 -6
- package/package.json +2 -2
|
@@ -274,14 +274,13 @@ export class RuntimeService {
|
|
|
274
274
|
return normalizedRoot.includes(marker);
|
|
275
275
|
}
|
|
276
276
|
async scheduleWindowsSelfUpdate(push, action) {
|
|
277
|
-
const npm = await resolveNpmLaunch();
|
|
278
277
|
const controlPlaneScript = path.join(path.dirname(fileURLToPath(import.meta.url)), "index.js");
|
|
279
278
|
const jobId = `${Date.now()}-${process.pid}`;
|
|
280
279
|
const logPath = path.join(os.tmpdir(), `${PACKAGE_NAME}-${action}-${jobId}.log`);
|
|
281
280
|
const scriptPath = path.join(os.tmpdir(), `${PACKAGE_NAME}-${action}-${jobId}.cmd`);
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
281
|
+
const npmCmdPath = path.join(path.dirname(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
282
|
+
const npmCommand = await pathExists(npmCmdPath) ? `"${npmCmdPath}"` : "npm.cmd";
|
|
283
|
+
const installCommand = `${npmCommand} install -g ${PACKAGE_NAME}@latest`;
|
|
285
284
|
const script = [
|
|
286
285
|
"@echo off",
|
|
287
286
|
"setlocal",
|
package/dist/ibmi/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { Tools } from "./Tools.js";
|
|
5
5
|
import { IBMiContent } from "./IBMiContent.js";
|
|
6
6
|
const LOCALE = "LC_ALL=EN_US.UTF-8";
|
|
7
|
+
const SQL_HEREDOC_TAG = "__MCP_FOR_I_SQL__";
|
|
7
8
|
export class IBMiClient {
|
|
8
9
|
client;
|
|
9
10
|
content;
|
|
@@ -110,16 +111,26 @@ export class IBMiClient {
|
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
async runSQL(statements) {
|
|
113
|
-
const input = Tools.fixSQL(statements, true);
|
|
114
|
+
const input = Tools.fixSQL(statements, true).trim();
|
|
115
|
+
if (!input)
|
|
116
|
+
return [];
|
|
114
117
|
const ccsid = this.config.sqlJobCcsid ?? 1208;
|
|
115
|
-
const command =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
const command = [
|
|
119
|
+
`system "CHGJOB CCSID(${ccsid})"`,
|
|
120
|
+
`cat <<'${SQL_HEREDOC_TAG}' | system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`,
|
|
121
|
+
input,
|
|
122
|
+
SQL_HEREDOC_TAG
|
|
123
|
+
].join("\n");
|
|
124
|
+
const output = await this.sendQsh({
|
|
125
|
+
command,
|
|
126
|
+
env: { QIBM_PASE_CCSID: String(ccsid) }
|
|
127
|
+
});
|
|
128
|
+
if (output.stdout && output.stdout.trim()) {
|
|
121
129
|
return Tools.db2Parse(output.stdout, input);
|
|
122
130
|
}
|
|
131
|
+
if (hasSqlError(output.stderr || "")) {
|
|
132
|
+
throw new Tools.SqlError(normalizeSqlError(output.stderr || ""));
|
|
133
|
+
}
|
|
123
134
|
return [];
|
|
124
135
|
}
|
|
125
136
|
async loadDefaultLibraryList() {
|
|
@@ -211,3 +222,13 @@ export class IBMiClient {
|
|
|
211
222
|
this.remoteFeatures["QZDFMDB2.PGM"] = "/QSYS.LIB/QZDFMDB2.PGM";
|
|
212
223
|
}
|
|
213
224
|
}
|
|
225
|
+
function hasSqlError(stderr) {
|
|
226
|
+
return /(SQL\d{4}|SQLSTATE|CLI ERROR)/i.test(stderr);
|
|
227
|
+
}
|
|
228
|
+
function normalizeSqlError(stderr) {
|
|
229
|
+
return stderr
|
|
230
|
+
.split(/\r?\n/)
|
|
231
|
+
.map(line => line.trim())
|
|
232
|
+
.filter(line => line && !line.includes("Object is EN_US.UTF-8"))
|
|
233
|
+
.join(" ");
|
|
234
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getTools, handleTool } from "./mcp/tools.js";
|
|
|
6
6
|
import { McpContext } from "./mcp/context.js";
|
|
7
7
|
import { log } from "./mcp/logger.js";
|
|
8
8
|
import { validateToolInput } from "./mcp/validation.js";
|
|
9
|
-
const server = new Server({ name: "mcp-for-i", version: "0.1.
|
|
9
|
+
const server = new Server({ name: "mcp-for-i", version: "0.1.4" }, { capabilities: { tools: {} } });
|
|
10
10
|
const ctx = new McpContext();
|
|
11
11
|
const tools = getTools();
|
|
12
12
|
const toolMap = new Map(tools.map(tool => [tool.name, tool]));
|
package/dist/mcp/tools.js
CHANGED
|
@@ -2,13 +2,12 @@ import { LocalLanguageActions } from "../ibmi/LocalLanguageActions.js";
|
|
|
2
2
|
import { CompileTools } from "../ibmi/CompileTools.js";
|
|
3
3
|
import { Search } from "../ibmi/Search.js";
|
|
4
4
|
import { Tools } from "../ibmi/Tools.js";
|
|
5
|
-
import { IBMiClient } from "../ibmi/client.js";
|
|
6
5
|
import { ConnectionService } from "../controlplane/connectionService.js";
|
|
7
6
|
import { log } from "./logger.js";
|
|
8
7
|
import fs from "fs/promises";
|
|
9
8
|
import path from "path";
|
|
10
9
|
export function getTools() {
|
|
11
|
-
|
|
10
|
+
const tools = [
|
|
12
11
|
{
|
|
13
12
|
name: "ibmi.connect",
|
|
14
13
|
description: "Connect to a saved IBM i connection by name.",
|
|
@@ -563,6 +562,7 @@ export function getTools() {
|
|
|
563
562
|
inputSchema: { type: "object", properties: {} }
|
|
564
563
|
}
|
|
565
564
|
];
|
|
565
|
+
return tools.map(withGuardedApprovalHint);
|
|
566
566
|
}
|
|
567
567
|
export async function handleTool(ctx, name, args) {
|
|
568
568
|
const connections = new ConnectionService(ctx.store);
|
|
@@ -685,13 +685,15 @@ export async function handleTool(ctx, name, args) {
|
|
|
685
685
|
}
|
|
686
686
|
case "ibmi.ifs.write": {
|
|
687
687
|
const conn = ctx.ensureActive(args?.connectionName);
|
|
688
|
-
|
|
688
|
+
const op = resolveIfsWriteOperation(args.path);
|
|
689
|
+
enforceWritable(ctx, conn, op, args);
|
|
689
690
|
await conn.content.writeStreamfileRaw(args.path, args.content);
|
|
690
691
|
return result("OK");
|
|
691
692
|
}
|
|
692
693
|
case "ibmi.ifs.mkdir": {
|
|
693
694
|
const conn = ctx.ensureActive(args?.connectionName);
|
|
694
|
-
|
|
695
|
+
const op = resolveIfsWriteOperation(args.path);
|
|
696
|
+
enforceWritable(ctx, conn, op, args);
|
|
695
697
|
await conn.sendCommand({ command: `mkdir -p ${Tools.escapePath(args.path)}` });
|
|
696
698
|
return result("OK");
|
|
697
699
|
}
|
|
@@ -704,7 +706,8 @@ export async function handleTool(ctx, name, args) {
|
|
|
704
706
|
}
|
|
705
707
|
case "ibmi.ifs.upload": {
|
|
706
708
|
const conn = ctx.ensureActive(args?.connectionName);
|
|
707
|
-
|
|
709
|
+
const op = resolveIfsWriteOperation(args.remotePath);
|
|
710
|
+
enforceWritable(ctx, conn, op, args);
|
|
708
711
|
await conn.client.putFile(args.localPath, args.remotePath);
|
|
709
712
|
return result("OK");
|
|
710
713
|
}
|
|
@@ -1125,7 +1128,13 @@ export async function handleTool(ctx, name, args) {
|
|
|
1125
1128
|
cmdResult = await conn.sendQsh({ command, directory: cwd });
|
|
1126
1129
|
}
|
|
1127
1130
|
else {
|
|
1128
|
-
|
|
1131
|
+
try {
|
|
1132
|
+
await conn.runSQL(`CALL QSYS2.QCMDEXC(${Tools.sqlString(command)})`);
|
|
1133
|
+
cmdResult = { code: 0, signal: null, stdout: "", stderr: "", command };
|
|
1134
|
+
}
|
|
1135
|
+
catch (err) {
|
|
1136
|
+
cmdResult = { code: 1, signal: null, stdout: "", stderr: err?.message || String(err), command };
|
|
1137
|
+
}
|
|
1129
1138
|
}
|
|
1130
1139
|
log("info", "audit.cl.run", { connectionName: args?.connectionName || ctx.activeName, environment });
|
|
1131
1140
|
return json(cmdResult);
|
|
@@ -1311,6 +1320,47 @@ function isDeniedByPolicy(policy, operation, args) {
|
|
|
1311
1320
|
}
|
|
1312
1321
|
return false;
|
|
1313
1322
|
}
|
|
1323
|
+
const APPROVAL_HINT_TOOLS = new Set([
|
|
1324
|
+
"ibmi.qsys.members.write",
|
|
1325
|
+
"ibmi.qsys.members.create",
|
|
1326
|
+
"ibmi.qsys.members.rename",
|
|
1327
|
+
"ibmi.qsys.members.delete",
|
|
1328
|
+
"ibmi.qsys.sourcefiles.create",
|
|
1329
|
+
"ibmi.qsys.libraries.create",
|
|
1330
|
+
"ibmi.ifs.write",
|
|
1331
|
+
"ibmi.ifs.mkdir",
|
|
1332
|
+
"ibmi.ifs.delete",
|
|
1333
|
+
"ibmi.ifs.upload",
|
|
1334
|
+
"ibmi.actions.run",
|
|
1335
|
+
"ibmi.profiles.activate",
|
|
1336
|
+
"ibmi.deploy.uploadDirectory",
|
|
1337
|
+
"ibmi.deploy.uploadFiles",
|
|
1338
|
+
"ibmi.deploy.setCcsid",
|
|
1339
|
+
"ibmi.deploy.sync",
|
|
1340
|
+
"ibmi.sql.execute",
|
|
1341
|
+
"ibmi.cl.run"
|
|
1342
|
+
]);
|
|
1343
|
+
function withGuardedApprovalHint(tool) {
|
|
1344
|
+
if (!APPROVAL_HINT_TOOLS.has(tool.name))
|
|
1345
|
+
return tool;
|
|
1346
|
+
const schema = tool.inputSchema || { type: "object", properties: {} };
|
|
1347
|
+
if (!schema.properties)
|
|
1348
|
+
schema.properties = {};
|
|
1349
|
+
if (!schema.properties.approve) {
|
|
1350
|
+
schema.properties.approve = {
|
|
1351
|
+
type: "boolean",
|
|
1352
|
+
description: "Set true to approve guarded-policy operations."
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
return { ...tool, inputSchema: schema };
|
|
1356
|
+
}
|
|
1357
|
+
function resolveIfsWriteOperation(pathValue) {
|
|
1358
|
+
const target = String(pathValue || "");
|
|
1359
|
+
if (/^\/qsys\.lib\//i.test(target)) {
|
|
1360
|
+
return "qsys.write";
|
|
1361
|
+
}
|
|
1362
|
+
return "ifs.write";
|
|
1363
|
+
}
|
|
1314
1364
|
function parseEvfevent(content) {
|
|
1315
1365
|
const diagnostics = [];
|
|
1316
1366
|
for (const rawLine of content.split(/\r?\n/)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-for-i",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|
|
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",
|
|
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"
|