mcp-for-i 0.1.10 → 0.1.11

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 (2) hide show
  1. package/dist/mcp/tools.js +53 -20
  2. package/package.json +1 -1
package/dist/mcp/tools.js CHANGED
@@ -1277,7 +1277,26 @@ export async function handleTool(ctx, name, args) {
1277
1277
  }
1278
1278
  case "ibmi.qsys.sourcefiles.list": {
1279
1279
  const conn = await ctx.ensureActive(args?.connectionName);
1280
- const files = await conn.content.getObjectList(args.library, ["*FILE"]);
1280
+ let files;
1281
+ try {
1282
+ files = await conn.content.getObjectList(args.library, ["*FILE"]);
1283
+ }
1284
+ catch {
1285
+ try {
1286
+ const allObjects = await conn.content.getObjectList(args.library, ["*ALL"]);
1287
+ files = allObjects.filter((obj) => String(obj?.type || "").toUpperCase() === "*FILE");
1288
+ }
1289
+ catch {
1290
+ const rows = await conn.runSQL(`select system_table_name as name, coalesce(table_text, '') as text from qsys2.systables where upper(system_table_schema)=${Tools.sqlString(String(args.library || "").toUpperCase())} fetch first 5000 rows only`);
1291
+ files = rows.map((row) => ({
1292
+ library: String(args.library || "").toUpperCase(),
1293
+ name: String(row?.NAME || row?.SYSTEM_TABLE_NAME || ""),
1294
+ type: "*FILE",
1295
+ text: String(row?.TEXT || row?.TABLE_TEXT || ""),
1296
+ attribute: ""
1297
+ }));
1298
+ }
1299
+ }
1281
1300
  const sourceByAttr = files.filter((obj) => {
1282
1301
  const attr = String(obj?.attribute || "").toUpperCase();
1283
1302
  return attr === "PF-SRC" || attr === "SOURCE";
@@ -2632,28 +2651,33 @@ async function queryJournalEntries(conn, options) {
2632
2651
  }
2633
2652
  async function queryJobs(conn, options) {
2634
2653
  const scanLimit = Math.min(options.limit * 5, 5000);
2635
- let activeRows = [];
2654
+ const rows = [];
2655
+ const activeWhere = buildJobWhereClause(options, {
2656
+ subsystem: "SUBSYSTEM",
2657
+ userName: "AUTHORIZATION_NAME",
2658
+ status: "JOB_STATUS"
2659
+ });
2636
2660
  try {
2637
- activeRows = await conn.runSQL(`select * from table(qsys2.active_job_info()) fetch first ${scanLimit} rows only`);
2661
+ const activeRows = await conn.runSQL(`select * from table(qsys2.active_job_info())${activeWhere} fetch first ${scanLimit} rows only`);
2662
+ rows.push(...activeRows.map((row) => normalizeActiveJobRow(row)));
2638
2663
  }
2639
2664
  catch {
2640
2665
  // Continue with job_info fallback.
2641
2666
  }
2642
- let rows = filterNormalizedJobs(activeRows.map((row) => normalizeActiveJobRow(row)), options);
2643
- if (rows.length >= options.limit)
2644
- return rows.slice(0, options.limit);
2667
+ const allWhere = buildJobWhereClause(options, {
2668
+ subsystem: "JOB_SUBSYSTEM",
2669
+ userName: "JOB_USER",
2670
+ status: "JOB_STATUS"
2671
+ });
2645
2672
  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);
2673
+ const allRows = await conn.runSQL(`select * from table(qsys2.job_info('*ALL'))${allWhere} fetch first ${scanLimit} rows only`);
2648
2674
  const seen = new Set(rows.map((row) => String(row.jobName || "")));
2649
- for (const row of fallback) {
2675
+ for (const row of allRows.map((raw) => normalizeActiveJobRow(raw))) {
2650
2676
  const key = String(row.jobName || "");
2651
2677
  if (!key || seen.has(key))
2652
2678
  continue;
2653
2679
  rows.push(row);
2654
2680
  seen.add(key);
2655
- if (rows.length >= options.limit)
2656
- break;
2657
2681
  }
2658
2682
  }
2659
2683
  catch {
@@ -2661,21 +2685,24 @@ async function queryJobs(conn, options) {
2661
2685
  }
2662
2686
  return rows.slice(0, options.limit);
2663
2687
  }
2664
- function filterNormalizedJobs(rows, options) {
2665
- let filtered = rows;
2688
+ function buildJobWhereClause(options, columns) {
2689
+ const predicates = [];
2666
2690
  if (options.subsystem) {
2667
2691
  const subsystem = String(options.subsystem).trim().toUpperCase();
2668
- filtered = filtered.filter((row) => (row.subsystem || "") === subsystem);
2692
+ if (subsystem)
2693
+ predicates.push(`upper(${columns.subsystem})=${Tools.sqlString(subsystem)}`);
2669
2694
  }
2670
2695
  if (options.userName) {
2671
2696
  const userName = String(options.userName).trim().toUpperCase();
2672
- filtered = filtered.filter((row) => (row.userName || "") === userName);
2697
+ if (userName)
2698
+ predicates.push(`upper(${columns.userName})=${Tools.sqlString(userName)}`);
2673
2699
  }
2674
2700
  if (options.status) {
2675
- const status = String(options.status).trim().toUpperCase();
2676
- filtered = filtered.filter((row) => (row.status || "").includes(status));
2701
+ const status = String(options.status).trim().toUpperCase().replace(/'/g, "''");
2702
+ if (status)
2703
+ predicates.push(`upper(${columns.status}) like '%${status}%'`);
2677
2704
  }
2678
- return filtered;
2705
+ return predicates.length > 0 ? ` where ${predicates.join(" and ")}` : "";
2679
2706
  }
2680
2707
  async function querySubsystemSummary(conn, limit) {
2681
2708
  const jobs = await queryJobs(conn, { limit: 5000 });
@@ -2713,11 +2740,14 @@ async function queryMessageQueueEntries(conn, options) {
2713
2740
  }
2714
2741
  async function querySpoolEntries(conn, limit) {
2715
2742
  const queries = [
2743
+ `select * from qsys2.output_queue_entries fetch first ${limit} rows only`,
2744
+ `select * from table(qsys2.output_queue_entries()) fetch first ${limit} rows only`,
2716
2745
  `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
2746
  `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
2747
  `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
2748
  `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`
2749
+ `select * from qsys2.spooled_file_info fetch first ${limit} rows only`,
2750
+ `select * from table(qsys2.spooled_file_info()) fetch first ${limit} rows only`
2721
2751
  ];
2722
2752
  let lastError;
2723
2753
  for (const sql of queries) {
@@ -2848,7 +2878,10 @@ function normalizeDataAreaInfoRow(row) {
2848
2878
  function buildSpoolCommand(commandName, args) {
2849
2879
  const jobName = requireJobName(args.jobName);
2850
2880
  const spooledFileName = requireIbmObjectName(args.spooledFileName, "spooledFileName");
2851
- const spooledFileNumber = clampNumber(args.spooledFileNumber, 1, 1, 999999999);
2881
+ const rawNumber = Number(args.spooledFileNumber);
2882
+ const spooledFileNumber = Number.isFinite(rawNumber) && rawNumber > 0
2883
+ ? String(Math.floor(rawNumber))
2884
+ : "*LAST";
2852
2885
  return `${commandName} FILE(${spooledFileName}) JOB(${jobName}) SPLNBR(${spooledFileNumber})`;
2853
2886
  }
2854
2887
  async function queryJournalEntriesViaDisplayJournal(conn, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-for-i",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "description": "Local MCP server and control plane for IBM i workflows",
6
6
  "type": "module",