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.
Files changed (67) hide show
  1. package/README.md +17 -0
  2. package/dist/config/store.js +229 -109
  3. package/dist/controlplane/auth.js +128 -0
  4. package/dist/controlplane/clientIntegrationService.js +83 -4
  5. package/dist/controlplane/commands.js +14 -9
  6. package/dist/controlplane/connectionService.js +110 -5
  7. package/dist/controlplane/connectionTest.js +329 -0
  8. package/dist/controlplane/errors.js +12 -0
  9. package/dist/controlplane/runtimeService.js +98 -26
  10. package/dist/controlplane/server.js +122 -11
  11. package/dist/controlplane/serverShared.js +25 -6
  12. package/dist/controlplane/serviceManager.js +113 -100
  13. package/dist/controlplane/ui.js +1742 -920
  14. package/dist/ibmi/CompileTools.js +29 -1
  15. package/dist/ibmi/IBMiContent.js +435 -63
  16. package/dist/ibmi/IbmDocs.js +685 -0
  17. package/dist/ibmi/LocalLanguageActions.js +129 -3
  18. package/dist/ibmi/Search.js +158 -52
  19. package/dist/ibmi/Tools.js +7 -10
  20. package/dist/ibmi/client.js +220 -31
  21. package/dist/ibmi/evfevent.js +256 -0
  22. package/dist/ibmi/knownHosts.js +190 -0
  23. package/dist/ibmi/libraryList.js +93 -0
  24. package/dist/ibmi/sourceInspect.js +815 -0
  25. package/dist/ibmi/sqlJob.js +888 -0
  26. package/dist/ibmi/sqlParse.js +317 -0
  27. package/dist/ibmi/tn5250/certTrust.js +59 -0
  28. package/dist/ibmi/tn5250/client.js +310 -0
  29. package/dist/ibmi/tn5250/datastream.js +109 -0
  30. package/dist/ibmi/tn5250/ebcdic.js +74 -0
  31. package/dist/ibmi/tn5250/index.js +6 -0
  32. package/dist/ibmi/tn5250/screen.js +827 -0
  33. package/dist/ibmi/tn5250/telnet.js +186 -0
  34. package/dist/ibmi/variables.js +18 -7
  35. package/dist/index.js +43 -22
  36. package/dist/mcp/annotations.js +155 -0
  37. package/dist/mcp/audit.js +490 -133
  38. package/dist/mcp/confirm.js +48 -0
  39. package/dist/mcp/context.js +43 -5
  40. package/dist/mcp/deployMethods.js +192 -0
  41. package/dist/mcp/logger.js +5 -10
  42. package/dist/mcp/modules/bulkMembers.js +476 -0
  43. package/dist/mcp/modules/changedLines.js +189 -0
  44. package/dist/mcp/modules/compare.js +198 -0
  45. package/dist/mcp/modules/compile.js +515 -0
  46. package/dist/mcp/modules/ifsOps.js +123 -0
  47. package/dist/mcp/modules/index.js +38 -0
  48. package/dist/mcp/modules/inspect.js +811 -0
  49. package/dist/mcp/modules/libl.js +246 -0
  50. package/dist/mcp/modules/members.js +171 -0
  51. package/dist/mcp/modules/objects.js +339 -0
  52. package/dist/mcp/modules/system.js +266 -0
  53. package/dist/mcp/modules/tn5250.js +515 -0
  54. package/dist/mcp/policy.js +412 -0
  55. package/dist/mcp/tools.js +1700 -698
  56. package/dist/security/credentialStore.js +62 -13
  57. package/dist/security/localPaths.js +138 -0
  58. package/dist/security/redact.js +79 -0
  59. package/dist/util/atomicWrite.js +101 -0
  60. package/dist/util/fileLock.js +206 -0
  61. package/docs/capabilities.md +50 -7
  62. package/docs/configuration.md +189 -60
  63. package/docs/examples.md +109 -73
  64. package/docs/overview.md +15 -15
  65. package/docs/security.md +101 -16
  66. package/docs/tools.md +363 -188
  67. package/package.json +56 -53
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Telnet layer for TN5250 (RFC 854, RFC 1205, RFC 2877, RFC 4777).
3
+ *
4
+ * TelnetParser turns raw socket bytes into negotiation events and complete
5
+ * 5250 records (data between IAC EOR markers, with IAC IAC unescaped).
6
+ */
7
+ export const IAC = 255;
8
+ export const DONT = 254;
9
+ export const DO = 253;
10
+ export const WONT = 252;
11
+ export const WILL = 251;
12
+ export const SB = 250;
13
+ export const SE = 240;
14
+ export const EOR = 239;
15
+ export const OPT_BINARY = 0;
16
+ export const OPT_TERMINAL_TYPE = 24;
17
+ export const OPT_EOR = 25;
18
+ export const OPT_NEW_ENVIRON = 39;
19
+ // Subnegotiation codes
20
+ export const TT_IS = 0;
21
+ export const TT_SEND = 1;
22
+ export const ENV_IS = 0;
23
+ export const ENV_SEND = 1;
24
+ export const ENV_INFO = 2;
25
+ export const ENV_VAR = 0;
26
+ export const ENV_VALUE = 1;
27
+ export const ENV_ESC = 2;
28
+ export const ENV_USERVAR = 3;
29
+ export class TelnetParser {
30
+ state = "data";
31
+ command = 0;
32
+ record = [];
33
+ sb = [];
34
+ /** Feed socket bytes; returns the events completed by this chunk. */
35
+ push(chunk) {
36
+ const events = [];
37
+ for (const byte of chunk) {
38
+ switch (this.state) {
39
+ case "data":
40
+ if (byte === IAC)
41
+ this.state = "iac";
42
+ else
43
+ this.record.push(byte);
44
+ break;
45
+ case "iac":
46
+ if (byte === IAC) {
47
+ this.record.push(IAC);
48
+ this.state = "data";
49
+ }
50
+ else if (byte === EOR) {
51
+ events.push({ type: "record", data: Buffer.from(this.record) });
52
+ this.record = [];
53
+ this.state = "data";
54
+ }
55
+ else if (byte === DO || byte === DONT || byte === WILL || byte === WONT) {
56
+ this.command = byte;
57
+ this.state = "option";
58
+ }
59
+ else if (byte === SB) {
60
+ this.sb = [];
61
+ this.state = "sb";
62
+ }
63
+ else {
64
+ // NOP, GA and other two-byte commands carry nothing we need.
65
+ this.state = "data";
66
+ }
67
+ break;
68
+ case "option":
69
+ events.push({ type: "command", command: this.command, option: byte });
70
+ this.state = "data";
71
+ break;
72
+ case "sb":
73
+ if (byte === IAC)
74
+ this.state = "sb-iac";
75
+ else
76
+ this.sb.push(byte);
77
+ break;
78
+ case "sb-iac":
79
+ if (byte === SE) {
80
+ const [option, ...rest] = this.sb;
81
+ events.push({ type: "subnegotiation", option: option ?? -1, data: Buffer.from(rest) });
82
+ this.sb = [];
83
+ this.state = "data";
84
+ }
85
+ else {
86
+ // IAC IAC inside a subnegotiation is a literal 0xFF.
87
+ this.sb.push(byte);
88
+ this.state = "sb";
89
+ }
90
+ break;
91
+ }
92
+ }
93
+ return events;
94
+ }
95
+ }
96
+ export function telnetCommand(command, option) {
97
+ return Buffer.from([IAC, command, option]);
98
+ }
99
+ /** Wrap a 5250 record for sending: double IAC bytes and append IAC EOR. */
100
+ export function frameRecord(record) {
101
+ const out = [];
102
+ for (const byte of record) {
103
+ out.push(byte);
104
+ if (byte === IAC)
105
+ out.push(IAC);
106
+ }
107
+ out.push(IAC, EOR);
108
+ return Buffer.from(out);
109
+ }
110
+ export function subnegotiation(option, payload) {
111
+ const out = [IAC, SB, option];
112
+ for (const byte of payload) {
113
+ out.push(byte);
114
+ if (byte === IAC)
115
+ out.push(IAC);
116
+ }
117
+ out.push(IAC, SE);
118
+ return Buffer.from(out);
119
+ }
120
+ export function terminalTypeIs(terminalType) {
121
+ return subnegotiation(OPT_TERMINAL_TYPE, Buffer.concat([Buffer.from([TT_IS]), Buffer.from(terminalType, "ascii")]));
122
+ }
123
+ /** Parse the body of a NEW-ENVIRON SEND (after the SEND byte). */
124
+ export function parseEnvSend(data) {
125
+ const requested = [];
126
+ let serverSeed;
127
+ let i = 0;
128
+ while (i < data.length) {
129
+ const kindByte = data[i];
130
+ if (kindByte !== ENV_VAR && kindByte !== ENV_USERVAR) {
131
+ i += 1;
132
+ continue;
133
+ }
134
+ i += 1;
135
+ // RFC 4777: the server's seed follows the IBMRSEED name directly
136
+ // (optionally after VALUE) and is 8 raw bytes.
137
+ if (data.subarray(i, i + 8).toString("ascii") === "IBMRSEED") {
138
+ i += 8;
139
+ if (data[i] === ENV_VALUE)
140
+ i += 1;
141
+ const seed = data.subarray(i, Math.min(i + 8, data.length));
142
+ if (seed.length > 0)
143
+ serverSeed = Buffer.from(seed);
144
+ i += seed.length;
145
+ requested.push({ kind: kindByte === ENV_VAR ? "var" : "uservar", name: "IBMRSEED" });
146
+ continue;
147
+ }
148
+ const name = [];
149
+ let inValue = false;
150
+ while (i < data.length && data[i] !== ENV_VAR && data[i] !== ENV_USERVAR) {
151
+ if (data[i] === ENV_VALUE) {
152
+ inValue = true;
153
+ i += 1;
154
+ continue;
155
+ }
156
+ if (data[i] === ENV_ESC && i + 1 < data.length)
157
+ i += 1;
158
+ if (!inValue)
159
+ name.push(data[i]);
160
+ i += 1;
161
+ }
162
+ // An empty name means "send all variables of this kind".
163
+ requested.push({ kind: kindByte === ENV_VAR ? "var" : "uservar", name: Buffer.from(name).toString("ascii") });
164
+ }
165
+ return { requested, serverSeed };
166
+ }
167
+ function escapeEnv(bytes) {
168
+ const out = [];
169
+ for (const byte of bytes) {
170
+ if (byte === ENV_VAR || byte === ENV_VALUE || byte === ENV_ESC || byte === ENV_USERVAR)
171
+ out.push(ENV_ESC);
172
+ out.push(byte);
173
+ }
174
+ return out;
175
+ }
176
+ /** Build IAC SB NEW-ENVIRON IS ... IAC SE. */
177
+ export function envIs(values) {
178
+ const out = [ENV_IS];
179
+ for (const entry of values) {
180
+ out.push(entry.kind === "var" ? ENV_VAR : ENV_USERVAR);
181
+ out.push(...escapeEnv(Buffer.from(entry.name, "ascii")));
182
+ out.push(ENV_VALUE);
183
+ out.push(...escapeEnv(entry.value));
184
+ }
185
+ return subnegotiation(OPT_NEW_ENVIRON, Buffer.from(out));
186
+ }
@@ -31,14 +31,25 @@ export class Variables extends Map {
31
31
  super.set(key, this.expand(value, [key]));
32
32
  }
33
33
  }
34
+ /**
35
+ * Replace every variable in one pass. Longer names are tried first and a
36
+ * name that ends in a letter or digit only matches when the next character
37
+ * is not part of a name, so `&EXT` never replaces the start of `&EXTL` and
38
+ * `&OPENLIB` never replaces the start of `&OPENLIBL`.
39
+ */
34
40
  expand(input, keysToOmit = []) {
35
- for (const [key, value] of this.entries()) {
36
- if (!keysToOmit.includes(key)) {
37
- const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38
- input = input.replace(new RegExp(escapedKey, `g`), value);
39
- }
40
- }
41
- return input;
41
+ const keys = Array.from(this.keys())
42
+ .filter(key => key.length > 0 && !keysToOmit.includes(key))
43
+ .sort((a, b) => b.length - a.length);
44
+ if (keys.length === 0)
45
+ return input;
46
+ const pattern = keys
47
+ .map(key => {
48
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49
+ return /[A-Za-z0-9_#@$]$/.test(key) ? `${escaped}(?![A-Za-z0-9_#@$])` : escaped;
50
+ })
51
+ .join("|");
52
+ return input.replace(new RegExp(pattern, "g"), match => this.get(match) ?? match);
42
53
  }
43
54
  toPaseVariables() {
44
55
  const variables = {};
package/dist/index.js CHANGED
@@ -4,13 +4,17 @@ import fs from "fs";
4
4
  import { fileURLToPath } from "url";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { CallToolRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
8
  import { getTools, handleTool } from "./mcp/tools.js";
9
9
  import { McpContext } from "./mcp/context.js";
10
+ import { createElicitationConfirm } from "./mcp/confirm.js";
10
11
  import { log } from "./mcp/logger.js";
12
+ import { redactSecrets, redactSecretsInText } from "./security/redact.js";
11
13
  import { validateToolInput } from "./mcp/validation.js";
12
14
  import { appendAuditRecord } from "./mcp/audit.js";
15
+ import { redactModuleArgs } from "./mcp/modules/index.js";
13
16
  import { runControlCommand } from "./controlplane/commands.js";
17
+ import { ibmDocs } from "./ibmi/IbmDocs.js";
14
18
  const argv = process.argv.slice(2);
15
19
  const firstArg = (argv[0] || "").toLowerCase();
16
20
  const packageVersion = readPackageVersion();
@@ -29,17 +33,50 @@ if (firstArg === "--help" || firstArg === "-h" || firstArg === "help") {
29
33
  printHelp();
30
34
  process.exit(0);
31
35
  }
32
- const server = new Server({ name: "mcp-for-i", version: packageVersion }, { capabilities: { tools: {} } });
36
+ const server = new Server({ name: "mcp-for-i", version: packageVersion }, { capabilities: { tools: {}, resources: {} } });
33
37
  const ctx = new McpContext();
38
+ ctx.confirm = createElicitationConfirm(server);
34
39
  const tools = getTools();
35
40
  const toolMap = new Map(tools.map(tool => [tool.name, tool]));
36
41
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
37
42
  tools
38
43
  }));
44
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
45
+ resources: [
46
+ {
47
+ uri: "ibmi-docs://catalog",
48
+ name: "IBM i documentation catalog",
49
+ description: "Release-aware allowlist of official IBM i documentation available through mcp-for-i.",
50
+ mimeType: "application/json"
51
+ },
52
+ ...ibmDocs.listResources()
53
+ ]
54
+ }));
55
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
56
+ resourceTemplates: [{
57
+ uriTemplate: "ibmi-docs://topic/{ref}",
58
+ name: "Official IBM i documentation topic",
59
+ description: "Open a versioned official IBM i documentation topic using a ref from ibmi.docs.search.",
60
+ mimeType: "text/markdown"
61
+ }]
62
+ }));
63
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
64
+ const uri = request.params.uri;
65
+ const text = await ibmDocs.readResource(uri);
66
+ return {
67
+ contents: [{
68
+ uri,
69
+ mimeType: uri === "ibmi-docs://catalog" ? "application/json" : "text/markdown",
70
+ text
71
+ }]
72
+ };
73
+ });
39
74
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
40
75
  const { name, arguments: args } = request.params;
41
76
  const correlationId = crypto.randomUUID();
42
77
  const startedAt = Date.now();
78
+ // Computed before the call: module redaction depends on the state the call starts from.
79
+ let redactedArgs;
43
80
  try {
44
81
  const toolDef = toolMap.get(name);
45
82
  if (!toolDef)
@@ -48,7 +85,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
48
85
  if (errors.length > 0) {
49
86
  throw new Error(`Invalid arguments: ${errors.join("; ")}`);
50
87
  }
51
- const redactedArgs = redactSensitive(args || {});
88
+ redactedArgs = redactSensitive(redactModuleArgs(ctx, name, args || {}));
52
89
  log("info", "tool.call", { name, correlationId, args: redactedArgs });
53
90
  const result = await handleTool(ctx, name, args || {});
54
91
  const durationMs = Date.now() - startedAt;
@@ -72,12 +109,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
109
  await safeAppendAudit({
73
110
  tool: name,
74
111
  status: "error",
75
- args: redactSensitive(args || {}),
112
+ args: redactedArgs ?? redactSensitive(redactModuleArgs(ctx, name, args || {})),
76
113
  connectionName: resolveConnectionName(name, args || {}, ctx.activeName),
77
114
  approve: Boolean(args && typeof args === "object" && args.approve),
78
115
  durationMs,
79
116
  correlationId,
80
- error: errorMessage
117
+ error: redactSecretsInText(errorMessage)
81
118
  });
82
119
  return {
83
120
  isError: true,
@@ -88,23 +125,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
88
125
  const transport = new StdioServerTransport();
89
126
  await server.connect(transport);
90
127
  function redactSensitive(input) {
91
- const secrets = new Set(["password", "passphrase", "secret", "token", "apikey", "apiKey", "authorization"]);
92
- if (Array.isArray(input)) {
93
- return input.map(redactSensitive);
94
- }
95
- if (typeof input === "object" && input !== null) {
96
- const out = {};
97
- for (const [key, value] of Object.entries(input)) {
98
- if (secrets.has(key)) {
99
- out[key] = "***REDACTED***";
100
- }
101
- else {
102
- out[key] = redactSensitive(value);
103
- }
104
- }
105
- return out;
106
- }
107
- return input;
128
+ return redactSecrets(input);
108
129
  }
109
130
  function summarizeResult(result) {
110
131
  const contentCount = Array.isArray(result?.content) ? result.content.length : 0;
@@ -0,0 +1,155 @@
1
+ // Tools that only read from the IBM i, the local config or the audit log.
2
+ const READ_ONLY = new Set([
3
+ "ibmi.session.list",
4
+ "ibmi.session.status",
5
+ "ibmi.session.keepalive",
6
+ "ibmi.session.bootstrap",
7
+ "ibmi.connections.list",
8
+ "ibmi.qsys.libraries.list",
9
+ "ibmi.qsys.objects.list",
10
+ "ibmi.qsys.sourcefiles.list",
11
+ "ibmi.qsys.members.list",
12
+ "ibmi.qsys.members.read",
13
+ "ibmi.ifs.list",
14
+ "ibmi.ifs.read",
15
+ "ibmi.search.members",
16
+ "ibmi.source.resolve",
17
+ "ibmi.source.inspect",
18
+ "ibmi.search.ifs",
19
+ "ibmi.find.ifs",
20
+ "ibmi.actions.list",
21
+ "ibmi.libl.get",
22
+ "ibmi.libl.validate",
23
+ "ibmi.profiles.list",
24
+ "ibmi.resolve.path",
25
+ "ibmi.filters.list",
26
+ "ibmi.ifs.shortcuts.list",
27
+ "ibmi.debug.status",
28
+ "ibmi.deploy.compare",
29
+ "ibmi.sql.query",
30
+ "ibmi.diagnostics.parseEvfevent",
31
+ "ibmi.joblog.get",
32
+ "ibmi.spool.list",
33
+ "ibmi.spool.read",
34
+ "ibmi.jobs.list",
35
+ "ibmi.subsystems.list",
36
+ "ibmi.subsystems.status",
37
+ "ibmi.msgq.read",
38
+ "ibmi.locks.list",
39
+ "ibmi.authority.object.get",
40
+ "ibmi.dataarea.read",
41
+ "ibmi.audit.list",
42
+ "ibmi.audit.verify",
43
+ "ibmi.journal.objects.list",
44
+ "ibmi.journal.entries.query",
45
+ "ibmi.qaudjrn.events.query",
46
+ "ibmi.tn5250.readScreen",
47
+ "ibmi.tn5250.waitFor",
48
+ "ibmi.tn5250.snapshot"
49
+ ]);
50
+ // Read-only tools that reach outside the IBM i and the local machine.
51
+ const OPEN_WORLD = new Set(["ibmi.docs.search", "ibmi.docs.open"]);
52
+ // Tools that can delete or overwrite data, end work, or run arbitrary commands.
53
+ const DESTRUCTIVE = new Set([
54
+ "ibmi.connections.delete",
55
+ "ibmi.qsys.members.write",
56
+ "ibmi.qsys.members.delete",
57
+ "ibmi.qsys.members.rename",
58
+ "ibmi.ifs.write",
59
+ "ibmi.ifs.delete",
60
+ "ibmi.ifs.upload",
61
+ "ibmi.ifs.download",
62
+ "ibmi.actions.run",
63
+ "ibmi.actions.delete",
64
+ "ibmi.profiles.delete",
65
+ "ibmi.filters.delete",
66
+ "ibmi.ifs.shortcuts.delete",
67
+ "ibmi.deploy.uploadDirectory",
68
+ "ibmi.deploy.uploadFiles",
69
+ "ibmi.deploy.sync",
70
+ "ibmi.sql.execute",
71
+ "ibmi.cl.run",
72
+ "ibmi.spool.delete",
73
+ "ibmi.jobs.end",
74
+ "ibmi.subsystems.end",
75
+ "ibmi.dataqueue.receive",
76
+ "ibmi.dataarea.write",
77
+ "ibmi.audit.export",
78
+ "ibmi.audit.purge",
79
+ "ibmi.journal.endPf",
80
+ "ibmi.journal.endIfs",
81
+ "ibmi.journal.receivers.retention",
82
+ "ibmi.compliance.report.generate",
83
+ "ibmi.tn5250.sendKeys"
84
+ ]);
85
+ // Writes that leave the same end state when repeated with the same arguments.
86
+ const IDEMPOTENT_WRITES = new Set([
87
+ "ibmi.connect",
88
+ "ibmi.disconnect",
89
+ "ibmi.session.terminate",
90
+ "ibmi.connections.update",
91
+ "ibmi.qsys.members.write",
92
+ "ibmi.ifs.write",
93
+ "ibmi.ifs.mkdir",
94
+ "ibmi.ifs.upload",
95
+ "ibmi.ifs.download",
96
+ "ibmi.libl.set",
97
+ "ibmi.libl.setCurrent",
98
+ "ibmi.profiles.save",
99
+ "ibmi.profiles.activate",
100
+ "ibmi.actions.save",
101
+ "ibmi.filters.save",
102
+ "ibmi.deploy.setCcsid",
103
+ "ibmi.spool.hold",
104
+ "ibmi.spool.release",
105
+ "ibmi.jobs.hold",
106
+ "ibmi.jobs.release",
107
+ "ibmi.dataarea.write",
108
+ "ibmi.audit.export",
109
+ "ibmi.tn5250.connect",
110
+ "ibmi.tn5250.setField",
111
+ "ibmi.tn5250.disconnect"
112
+ ]);
113
+ export function annotationsFor(name) {
114
+ if (READ_ONLY.has(name)) {
115
+ return { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
116
+ }
117
+ if (OPEN_WORLD.has(name)) {
118
+ return { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
119
+ }
120
+ // Anything not listed is treated as a write; unknown tools default to destructive.
121
+ return {
122
+ readOnlyHint: false,
123
+ destructiveHint: DESTRUCTIVE.has(name) || !IDEMPOTENT_WRITES.has(name) && !isKnownNonDestructiveWrite(name),
124
+ idempotentHint: IDEMPOTENT_WRITES.has(name),
125
+ openWorldHint: false
126
+ };
127
+ }
128
+ function isKnownNonDestructiveWrite(name) {
129
+ return [
130
+ "ibmi.connections.add",
131
+ "ibmi.qsys.members.create",
132
+ "ibmi.qsys.sourcefiles.create",
133
+ "ibmi.qsys.libraries.create",
134
+ "ibmi.libl.add",
135
+ "ibmi.libl.remove",
136
+ "ibmi.ifs.shortcuts.add",
137
+ "ibmi.debug.startService",
138
+ "ibmi.debug.stopService",
139
+ "ibmi.spool.move",
140
+ "ibmi.subsystems.start",
141
+ "ibmi.msgq.send",
142
+ "ibmi.msgq.reply",
143
+ "ibmi.dataqueue.send",
144
+ "ibmi.journal.receiver.create",
145
+ "ibmi.journal.create",
146
+ "ibmi.journal.receiver.change",
147
+ "ibmi.journal.startPf",
148
+ "ibmi.journal.startIfs"
149
+ ].includes(name);
150
+ }
151
+ export function withAnnotations(tool) {
152
+ if (tool.annotations)
153
+ return tool;
154
+ return { ...tool, annotations: annotationsFor(tool.name) };
155
+ }