mcp-for-i 0.1.9 → 0.1.10
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/mcp/tools.js +113 -41
- package/package.json +1 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -1892,15 +1892,7 @@ export async function handleTool(ctx, name, args) {
|
|
|
1892
1892
|
case "ibmi.spool.list": {
|
|
1893
1893
|
const conn = await ctx.ensureActive(args?.connectionName);
|
|
1894
1894
|
const limit = clampNumber(args?.limit, 200, 1, 5000);
|
|
1895
|
-
|
|
1896
|
-
try {
|
|
1897
|
-
rows = await conn.runSQL(`select job_name, spooled_file_name, spooled_file_number, output_queue_name, total_pages, file_status from qsys2.output_queue_entries fetch first ${limit} rows only`);
|
|
1898
|
-
}
|
|
1899
|
-
catch (err) {
|
|
1900
|
-
if (!isMissingColumnError(err, "FILE_STATUS"))
|
|
1901
|
-
throw err;
|
|
1902
|
-
rows = await conn.runSQL(`select job_name, spooled_file_name, spooled_file_number, output_queue_name, total_pages, cast('' as varchar(10)) as file_status from qsys2.output_queue_entries fetch first ${limit} rows only`);
|
|
1903
|
-
}
|
|
1895
|
+
const rows = await querySpoolEntries(conn, limit);
|
|
1904
1896
|
return json(rows.map(normalizeSpoolEntry));
|
|
1905
1897
|
}
|
|
1906
1898
|
case "ibmi.spool.read": {
|
|
@@ -1959,7 +1951,7 @@ export async function handleTool(ctx, name, args) {
|
|
|
1959
1951
|
enforceWritable(ctx, conn, "qsys.write", args);
|
|
1960
1952
|
const jobName = requireJobName(args.jobName);
|
|
1961
1953
|
const hold = Boolean(args?.holdOnJobQueue) ? "*JOBQ" : "*NO";
|
|
1962
|
-
const command = `HLDJOB JOB(${
|
|
1954
|
+
const command = `HLDJOB JOB(${jobName}) HOLD(${hold})`;
|
|
1963
1955
|
const cmdResult = await runClCommand(conn, command, "ile", undefined, 45000);
|
|
1964
1956
|
return json({ ok: cmdResult.code === 0, command, ...cmdResult });
|
|
1965
1957
|
}
|
|
@@ -1967,7 +1959,7 @@ export async function handleTool(ctx, name, args) {
|
|
|
1967
1959
|
const conn = await ctx.ensureActive(args?.connectionName);
|
|
1968
1960
|
enforceWritable(ctx, conn, "qsys.write", args);
|
|
1969
1961
|
const jobName = requireJobName(args.jobName);
|
|
1970
|
-
const command = `RLSJOB JOB(${
|
|
1962
|
+
const command = `RLSJOB JOB(${jobName})`;
|
|
1971
1963
|
const cmdResult = await runClCommand(conn, command, "ile", undefined, 45000);
|
|
1972
1964
|
return json({ ok: cmdResult.code === 0, command, ...cmdResult });
|
|
1973
1965
|
}
|
|
@@ -1980,7 +1972,7 @@ export async function handleTool(ctx, name, args) {
|
|
|
1980
1972
|
throw new Error("option must be *CNTRLD or *IMMED");
|
|
1981
1973
|
}
|
|
1982
1974
|
const delaySeconds = clampNumber(args?.delaySeconds, 30, 0, 99999);
|
|
1983
|
-
const command = `ENDJOB JOB(${
|
|
1975
|
+
const command = `ENDJOB JOB(${jobName}) OPTION(${option}) DELAY(${delaySeconds})`;
|
|
1984
1976
|
const cmdResult = await runClCommand(conn, command, "ile", undefined, 45000);
|
|
1985
1977
|
return json({ ok: cmdResult.code === 0, command, ...cmdResult });
|
|
1986
1978
|
}
|
|
@@ -2042,9 +2034,12 @@ export async function handleTool(ctx, name, args) {
|
|
|
2042
2034
|
const message = String(args.message || "");
|
|
2043
2035
|
if (!message.trim())
|
|
2044
2036
|
throw new Error("message is required");
|
|
2045
|
-
const
|
|
2037
|
+
const mappedMessageType = messageType === "*INQ" ? "*INQ" : "*INFO";
|
|
2038
|
+
const command = mappedMessageType === "*INQ"
|
|
2039
|
+
? `SNDMSG MSG(${quoteClString(message)}) TOMSGQ(${library}/${messageQueue}) MSGTYPE(*INQ) RPYMSGQ(${library}/${messageQueue})`
|
|
2040
|
+
: `SNDMSG MSG(${quoteClString(message)}) TOMSGQ(${library}/${messageQueue}) MSGTYPE(*INFO)`;
|
|
2046
2041
|
const cmdResult = await runClCommand(conn, command, "ile", undefined, 45000);
|
|
2047
|
-
return json({ ok: cmdResult.code === 0, command, ...cmdResult });
|
|
2042
|
+
return json({ ok: cmdResult.code === 0, command, requestedMessageType: messageType, mappedMessageType, ...cmdResult });
|
|
2048
2043
|
}
|
|
2049
2044
|
case "ibmi.msgq.reply": {
|
|
2050
2045
|
const conn = await ctx.ensureActive(args?.connectionName);
|
|
@@ -2088,9 +2083,21 @@ export async function handleTool(ctx, name, args) {
|
|
|
2088
2083
|
const message = String(args.message || "");
|
|
2089
2084
|
if (!message)
|
|
2090
2085
|
throw new Error("message is required");
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2086
|
+
try {
|
|
2087
|
+
await conn.runSQL(`call qsys2.send_data_queue(${Tools.sqlString(message)}, ${Tools.sqlString(queue)}, ${Tools.sqlString(library)})`);
|
|
2088
|
+
return json({
|
|
2089
|
+
ok: true,
|
|
2090
|
+
method: "qsys2.send_data_queue",
|
|
2091
|
+
library,
|
|
2092
|
+
queue,
|
|
2093
|
+
messageLength: message.length
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
catch {
|
|
2097
|
+
const command = `SNDDTAQ DTAQ(${library}/${queue}) DTA(${quoteClString(message)})`;
|
|
2098
|
+
const cmdResult = await runClCommand(conn, command, "ile", undefined, 45000);
|
|
2099
|
+
return json({ ok: cmdResult.code === 0, method: "cl.snddtaq", command, ...cmdResult });
|
|
2100
|
+
}
|
|
2094
2101
|
}
|
|
2095
2102
|
case "ibmi.dataqueue.receive": {
|
|
2096
2103
|
const conn = await ctx.ensureActive(args?.connectionName);
|
|
@@ -2624,21 +2631,51 @@ async function queryJournalEntries(conn, options) {
|
|
|
2624
2631
|
return normalized.slice(0, options.limit);
|
|
2625
2632
|
}
|
|
2626
2633
|
async function queryJobs(conn, options) {
|
|
2627
|
-
const
|
|
2628
|
-
let
|
|
2634
|
+
const scanLimit = Math.min(options.limit * 5, 5000);
|
|
2635
|
+
let activeRows = [];
|
|
2636
|
+
try {
|
|
2637
|
+
activeRows = await conn.runSQL(`select * from table(qsys2.active_job_info()) fetch first ${scanLimit} rows only`);
|
|
2638
|
+
}
|
|
2639
|
+
catch {
|
|
2640
|
+
// Continue with job_info fallback.
|
|
2641
|
+
}
|
|
2642
|
+
let rows = filterNormalizedJobs(activeRows.map((row) => normalizeActiveJobRow(row)), options);
|
|
2643
|
+
if (rows.length >= options.limit)
|
|
2644
|
+
return rows.slice(0, options.limit);
|
|
2645
|
+
try {
|
|
2646
|
+
const allRows = await conn.runSQL(`select * from table(qsys2.job_info('*ALL')) fetch first ${scanLimit} rows only`);
|
|
2647
|
+
const fallback = filterNormalizedJobs(allRows.map((row) => normalizeActiveJobRow(row)), options);
|
|
2648
|
+
const seen = new Set(rows.map((row) => String(row.jobName || "")));
|
|
2649
|
+
for (const row of fallback) {
|
|
2650
|
+
const key = String(row.jobName || "");
|
|
2651
|
+
if (!key || seen.has(key))
|
|
2652
|
+
continue;
|
|
2653
|
+
rows.push(row);
|
|
2654
|
+
seen.add(key);
|
|
2655
|
+
if (rows.length >= options.limit)
|
|
2656
|
+
break;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
catch {
|
|
2660
|
+
// Some systems may not provide job_info as a table function.
|
|
2661
|
+
}
|
|
2662
|
+
return rows.slice(0, options.limit);
|
|
2663
|
+
}
|
|
2664
|
+
function filterNormalizedJobs(rows, options) {
|
|
2665
|
+
let filtered = rows;
|
|
2629
2666
|
if (options.subsystem) {
|
|
2630
2667
|
const subsystem = String(options.subsystem).trim().toUpperCase();
|
|
2631
|
-
|
|
2668
|
+
filtered = filtered.filter((row) => (row.subsystem || "") === subsystem);
|
|
2632
2669
|
}
|
|
2633
2670
|
if (options.userName) {
|
|
2634
2671
|
const userName = String(options.userName).trim().toUpperCase();
|
|
2635
|
-
|
|
2672
|
+
filtered = filtered.filter((row) => (row.userName || "") === userName);
|
|
2636
2673
|
}
|
|
2637
2674
|
if (options.status) {
|
|
2638
2675
|
const status = String(options.status).trim().toUpperCase();
|
|
2639
|
-
|
|
2676
|
+
filtered = filtered.filter((row) => (row.status || "").includes(status));
|
|
2640
2677
|
}
|
|
2641
|
-
return
|
|
2678
|
+
return filtered;
|
|
2642
2679
|
}
|
|
2643
2680
|
async function querySubsystemSummary(conn, limit) {
|
|
2644
2681
|
const jobs = await queryJobs(conn, { limit: 5000 });
|
|
@@ -2656,10 +2693,10 @@ function normalizeActiveJobRow(row) {
|
|
|
2656
2693
|
return {
|
|
2657
2694
|
source: "job",
|
|
2658
2695
|
jobName: toNullableString(pickFirst(row, ["JOB_NAME", "QUALIFIED_JOB_NAME", "JOB"])),
|
|
2659
|
-
subsystem: toNullableString(pickFirst(row, ["SUBSYSTEM", "SUBSYSTEM_NAME"])),
|
|
2696
|
+
subsystem: toNullableString(pickFirst(row, ["SUBSYSTEM", "SUBSYSTEM_NAME", "JOB_SUBSYSTEM"])),
|
|
2660
2697
|
status: toNullableString(pickFirst(row, ["JOB_STATUS", "STATUS"])),
|
|
2661
2698
|
function: toNullableString(pickFirst(row, ["FUNCTION", "CURRENT_FUNCTION"])),
|
|
2662
|
-
userName: toNullableString(pickFirst(row, ["AUTHORIZATION_NAME", "USER_NAME", "CURRENT_USER"])),
|
|
2699
|
+
userName: toNullableString(pickFirst(row, ["AUTHORIZATION_NAME", "USER_NAME", "CURRENT_USER", "JOB_USER"])),
|
|
2663
2700
|
type: toNullableString(pickFirst(row, ["JOB_TYPE", "TYPE"])),
|
|
2664
2701
|
cpuSeconds: toNullableNumber(pickFirst(row, ["CPU_TIME", "TOTAL_CPU_TIME", "ELAPSED_TOTAL_SECONDS"]))
|
|
2665
2702
|
};
|
|
@@ -2674,6 +2711,25 @@ async function queryMessageQueueEntries(conn, options) {
|
|
|
2674
2711
|
}
|
|
2675
2712
|
return rows.map(normalizeMessageQueueEntry);
|
|
2676
2713
|
}
|
|
2714
|
+
async function querySpoolEntries(conn, limit) {
|
|
2715
|
+
const queries = [
|
|
2716
|
+
`select job_name, spooled_file_name, spooled_file_number, output_queue_name, total_pages, file_status from qsys2.output_queue_entries fetch first ${limit} rows only`,
|
|
2717
|
+
`select job_name, spooled_file_name, spooled_file_number, output_queue_name, total_pages, cast('' as varchar(10)) as file_status from qsys2.output_queue_entries fetch first ${limit} rows only`,
|
|
2718
|
+
`select job_name, spooled_file_name, cast(0 as integer) as spooled_file_number, output_queue_name, total_pages, file_status from qsys2.output_queue_entries fetch first ${limit} rows only`,
|
|
2719
|
+
`select job_name, spooled_file_name, cast(0 as integer) as spooled_file_number, output_queue_name, total_pages, cast('' as varchar(10)) as file_status from qsys2.output_queue_entries fetch first ${limit} rows only`,
|
|
2720
|
+
`select * from qsys2.output_queue_entries fetch first ${limit} rows only`
|
|
2721
|
+
];
|
|
2722
|
+
let lastError;
|
|
2723
|
+
for (const sql of queries) {
|
|
2724
|
+
try {
|
|
2725
|
+
return await conn.runSQL(sql);
|
|
2726
|
+
}
|
|
2727
|
+
catch (err) {
|
|
2728
|
+
lastError = err;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError || "Unable to read spool entries"));
|
|
2732
|
+
}
|
|
2677
2733
|
function normalizeMessageQueueEntry(row) {
|
|
2678
2734
|
return {
|
|
2679
2735
|
source: "msgq",
|
|
@@ -2689,10 +2745,10 @@ function normalizeMessageQueueEntry(row) {
|
|
|
2689
2745
|
async function queryLockEntries(conn, options) {
|
|
2690
2746
|
let rows;
|
|
2691
2747
|
try {
|
|
2692
|
-
rows = await conn.runSQL(`select * from table(qsys2.object_lock_info()) fetch first ${Math.min(options.limit
|
|
2748
|
+
rows = await conn.runSQL(`select * from table(qsys2.object_lock_info()) fetch first ${Math.min(options.limit, 500)} rows only`);
|
|
2693
2749
|
}
|
|
2694
2750
|
catch {
|
|
2695
|
-
rows = await conn.runSQL(`select * from qsys2.object_lock_info fetch first ${Math.min(options.limit
|
|
2751
|
+
rows = await conn.runSQL(`select * from qsys2.object_lock_info fetch first ${Math.min(options.limit, 500)} rows only`);
|
|
2696
2752
|
}
|
|
2697
2753
|
let normalized = rows.map(normalizeLockEntry);
|
|
2698
2754
|
if (options.objectLibrary)
|
|
@@ -2742,10 +2798,20 @@ function normalizeObjectAuthorityRow(row) {
|
|
|
2742
2798
|
async function queryDataQueueEntries(conn, options) {
|
|
2743
2799
|
let rows;
|
|
2744
2800
|
try {
|
|
2745
|
-
rows = await conn.runSQL(`select * from table(qsys2.receive_data_queue(
|
|
2801
|
+
rows = await conn.runSQL(`select * from table(qsys2.receive_data_queue(${Tools.sqlString(options.queue)}, ${Tools.sqlString(options.library)}, ${Tools.sqlString(options.remove ? "YES" : "NO")}, ${options.waitSeconds})) fetch first 1 rows only`);
|
|
2746
2802
|
}
|
|
2747
2803
|
catch {
|
|
2748
|
-
|
|
2804
|
+
try {
|
|
2805
|
+
rows = await conn.runSQL(`select * from table(qsys2.receive_data_queue(${Tools.sqlString(options.queue)}, ${Tools.sqlString(options.library)})) fetch first 1 rows only`);
|
|
2806
|
+
}
|
|
2807
|
+
catch {
|
|
2808
|
+
try {
|
|
2809
|
+
rows = await conn.runSQL(`select * from table(qsys2.data_queue_entries(${Tools.sqlString(options.queue)}, ${Tools.sqlString(options.library)})) fetch first 1 rows only`);
|
|
2810
|
+
}
|
|
2811
|
+
catch {
|
|
2812
|
+
rows = await conn.runSQL(`select * from qsys2.data_queue_entries where upper(data_queue_name)=${Tools.sqlString(options.queue)} and upper(data_queue_library)=${Tools.sqlString(options.library)} fetch first 1 rows only`);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2749
2815
|
}
|
|
2750
2816
|
return rows.map(normalizeDataQueueEntry);
|
|
2751
2817
|
}
|
|
@@ -2783,7 +2849,7 @@ function buildSpoolCommand(commandName, args) {
|
|
|
2783
2849
|
const jobName = requireJobName(args.jobName);
|
|
2784
2850
|
const spooledFileName = requireIbmObjectName(args.spooledFileName, "spooledFileName");
|
|
2785
2851
|
const spooledFileNumber = clampNumber(args.spooledFileNumber, 1, 1, 999999999);
|
|
2786
|
-
return `${commandName} FILE(${spooledFileName}) JOB(${
|
|
2852
|
+
return `${commandName} FILE(${spooledFileName}) JOB(${jobName}) SPLNBR(${spooledFileNumber})`;
|
|
2787
2853
|
}
|
|
2788
2854
|
async function queryJournalEntriesViaDisplayJournal(conn, options) {
|
|
2789
2855
|
const sampleSize = Math.min(options.limit * 5, 5000);
|
|
@@ -3038,13 +3104,19 @@ function requireQualifiedObject(value, label) {
|
|
|
3038
3104
|
};
|
|
3039
3105
|
}
|
|
3040
3106
|
function requireMessageKey(value) {
|
|
3041
|
-
const
|
|
3042
|
-
if (!
|
|
3107
|
+
const raw = String(value || "").trim();
|
|
3108
|
+
if (!raw)
|
|
3043
3109
|
throw new Error("messageKey is required");
|
|
3044
|
-
|
|
3045
|
-
|
|
3110
|
+
const directHex = raw.toUpperCase();
|
|
3111
|
+
if (/^[0-9A-F]{4,64}$/.test(directHex)) {
|
|
3112
|
+
return `X'${directHex}'`;
|
|
3113
|
+
}
|
|
3114
|
+
const prefixed = raw.toUpperCase();
|
|
3115
|
+
const match = prefixed.match(/^X'([0-9A-F]{4,64})'$/);
|
|
3116
|
+
if (match) {
|
|
3117
|
+
return `X'${match[1]}'`;
|
|
3046
3118
|
}
|
|
3047
|
-
|
|
3119
|
+
throw new Error(`Invalid messageKey: ${value}`);
|
|
3048
3120
|
}
|
|
3049
3121
|
async function updateConnectionSettings(ctx, name, update) {
|
|
3050
3122
|
const conn = await ctx.store.getConnection(name);
|
|
@@ -3284,12 +3356,12 @@ function normalizeSpoolEntry(row) {
|
|
|
3284
3356
|
return {
|
|
3285
3357
|
...row,
|
|
3286
3358
|
source: "spool",
|
|
3287
|
-
jobName:
|
|
3288
|
-
spooledFileName:
|
|
3289
|
-
spooledFileNumber:
|
|
3290
|
-
outputQueue:
|
|
3291
|
-
totalPages:
|
|
3292
|
-
status:
|
|
3359
|
+
jobName: toNullableString(pickFirst(row, ["JOB_NAME", "JOB"])) || "",
|
|
3360
|
+
spooledFileName: toNullableString(pickFirst(row, ["SPOOLED_FILE_NAME", "SPOOLED_FILE", "SPLF_NAME"])) || "",
|
|
3361
|
+
spooledFileNumber: toNullableNumber(pickFirst(row, ["SPOOLED_FILE_NUMBER", "SPLF_NUMBER", "FILE_NUMBER"])),
|
|
3362
|
+
outputQueue: toNullableString(pickFirst(row, ["OUTPUT_QUEUE_NAME", "OUTQ_NAME", "OUTPUT_QUEUE"])) || "",
|
|
3363
|
+
totalPages: toNullableNumber(pickFirst(row, ["TOTAL_PAGES", "PAGES"])),
|
|
3364
|
+
status: toNullableString(pickFirst(row, ["FILE_STATUS", "STATUS"])) || ""
|
|
3293
3365
|
};
|
|
3294
3366
|
}
|
|
3295
3367
|
function normalizeSpoolLine(row) {
|