mcp-for-i 0.1.13 → 0.3.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 (72) hide show
  1. package/README.md +27 -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 +52 -11
  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 +178 -11
  11. package/dist/controlplane/serverShared.js +25 -6
  12. package/dist/controlplane/serviceManager.js +147 -100
  13. package/dist/controlplane/ui.js +1783 -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 +334 -0
  29. package/dist/ibmi/tn5250/datastream.js +109 -0
  30. package/dist/ibmi/tn5250/ebcdic.js +93 -0
  31. package/dist/ibmi/tn5250/ebcdicTables.js +27 -0
  32. package/dist/ibmi/tn5250/index.js +6 -0
  33. package/dist/ibmi/tn5250/screen.js +1033 -0
  34. package/dist/ibmi/tn5250/telnet.js +186 -0
  35. package/dist/ibmi/variables.js +18 -7
  36. package/dist/index.js +79 -24
  37. package/dist/mcp/annotations.js +155 -0
  38. package/dist/mcp/audit.js +490 -133
  39. package/dist/mcp/confirm.js +48 -0
  40. package/dist/mcp/context.js +43 -5
  41. package/dist/mcp/deployMethods.js +192 -0
  42. package/dist/mcp/logger.js +5 -10
  43. package/dist/mcp/modules/bulkMembers.js +476 -0
  44. package/dist/mcp/modules/changedLines.js +189 -0
  45. package/dist/mcp/modules/compare.js +198 -0
  46. package/dist/mcp/modules/compile.js +515 -0
  47. package/dist/mcp/modules/ifsOps.js +123 -0
  48. package/dist/mcp/modules/index.js +38 -0
  49. package/dist/mcp/modules/inspect.js +811 -0
  50. package/dist/mcp/modules/libl.js +246 -0
  51. package/dist/mcp/modules/members.js +171 -0
  52. package/dist/mcp/modules/objects.js +339 -0
  53. package/dist/mcp/modules/system.js +266 -0
  54. package/dist/mcp/modules/tn5250.js +800 -0
  55. package/dist/mcp/modules/tn5250App.js +194 -0
  56. package/dist/mcp/modules/tn5250Font.js +8 -0
  57. package/dist/mcp/modules/tn5250Live.js +221 -0
  58. package/dist/mcp/modules/tn5250LivePage.js +480 -0
  59. package/dist/mcp/policy.js +412 -0
  60. package/dist/mcp/tools.js +1719 -698
  61. package/dist/security/credentialStore.js +62 -13
  62. package/dist/security/localPaths.js +138 -0
  63. package/dist/security/redact.js +79 -0
  64. package/dist/util/atomicWrite.js +101 -0
  65. package/dist/util/fileLock.js +206 -0
  66. package/docs/capabilities.md +264 -220
  67. package/docs/configuration.md +193 -60
  68. package/docs/examples.md +109 -73
  69. package/docs/overview.md +15 -15
  70. package/docs/security.md +101 -16
  71. package/docs/tools.md +367 -188
  72. package/package.json +5 -2
@@ -1,60 +1,193 @@
1
- # Configuration
2
-
3
- Config file is stored under the user config directory:
4
- - Windows: `%APPDATA%\mcp-for-i\config.json`
5
- - macOS: `~/Library/Application Support/mcp-for-i/config.json`
6
- - Linux: `${XDG_CONFIG_HOME:-~/.config}/mcp-for-i/config.json`
7
-
8
- ## Settings
9
- Default settings (editable in the config file):
10
- - `readOnlyMode`: boolean
11
- - `tempLibrary`: string
12
- - `tempDir`: string
13
- - `autoClearTempData`: boolean
14
- - `sourceFileCCSID`: string (default `*FILE`)
15
- - `enableSourceDates`: boolean (Phase 2+)
16
- - `homeDirectory`: string
17
- - `libraryList`: string[]
18
- - `currentLibrary`: string
19
- - `customVariables`: list of `{ name, value }`
20
-
21
- ## Connections
22
- Connections are stored without passwords. Passwords are stored in OS keychain (or in-session fallback). Each connection can include optional `settings` overrides.
23
- The control plane health response and Overview page report whether password storage is persistent (`keychain`) or process-only (`session`).
24
- WSL users should note that Linux Secret Service is separate from Windows Credential Manager; if WSL has no working keychain, passwords must be re-entered after the process exits.
25
- Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage even when a keychain is available.
26
- Connections can also define policy, for example:
27
- - `read-only`
28
- - `guarded` (default; approvals required for risky operations)
29
- - `power-user`
30
-
31
- Example:
32
- ```json
33
- {
34
- "connections": [
35
- {
36
- "name": "DEV400",
37
- "host": "dev400.myco.com",
38
- "port": 22,
39
- "username": "DEVUSER",
40
- "privateKeyPath": "C:/keys/id_rsa",
41
- "settings": {
42
- "readOnlyMode": false
43
- },
44
- "policy": {
45
- "profile": "guarded",
46
- "requireApprovalFor": ["sql.write", "cl.run", "deploy.sync"]
47
- },
48
- "profiles": [
49
- {
50
- "name": "projectA",
51
- "currentLibrary": "PROJALIB",
52
- "libraryList": ["QSYS", "QGPL", "PROJALIB"],
53
- "customVariables": [{ "name": "ENV", "value": "DEV" }]
54
- }
55
- ],
56
- "currentProfile": "projectA"
57
- }
58
- ]
59
- }
60
- ```
1
+ # Configuration
2
+
3
+ Config file is stored under the user config directory:
4
+ - Windows: `%APPDATA%\mcp-for-i\config.json`
5
+ - macOS: `~/Library/Application Support/mcp-for-i/config.json`
6
+ - Linux: `${XDG_CONFIG_HOME:-~/.config}/mcp-for-i/config.json`
7
+
8
+ ### How the config file is written
9
+ - Defaults are created only when `config.json` does not exist (an empty file counts as missing).
10
+ - If the file exists but cannot be parsed, it is never overwritten. A copy is saved next to it as `config.json.corrupt-<timestamp>` and every config operation fails with an error naming the file, until the file is repaired or removed.
11
+ - Writes are atomic: the new content goes to a temporary file in the same folder, is flushed to disk, and is renamed over `config.json`. On Windows the rename is retried when antivirus or the search indexer briefly locks the file; there is no non-atomic fallback.
12
+ - Every change (connections, settings, actions, profiles) re-reads the file and writes it back while holding `config.json.lock`, so the MCP server, the control plane and the CLI can update the config at the same time without losing each other's changes. A lock left behind by a crashed process is taken over after 30 seconds, or immediately if its process no longer exists.
13
+
14
+ ## Settings
15
+ Default settings (editable in the config file):
16
+ - `readOnlyMode`: boolean. When true, every tool that changes the system is refused, including `ibmi.sql.execute`, `ibmi.cl.run` and every AID key sent by `ibmi.tn5250.sendKeys`.
17
+ - `tempLibrary`: string
18
+ - `tempDir`: string
19
+ - `autoClearTempData`: boolean
20
+ - `sourceFileCCSID`: string (default `*FILE`)
21
+ - `enableSourceDates`: boolean (Phase 2+)
22
+ - `sourceReadMaxBytes`: number (default `2000000`): largest member, in UTF-8 bytes after trimming trailing blanks, that a full `members.read` returns. Larger members fail with `SOURCE_TOO_LARGE` and the agent is told to read line ranges or search instead. `0` disables the byte cap.
23
+ - `sourceReadMaxLines`: number (default `50000`): the same cap in lines. Checked against the catalog row count before any content is downloaded. `0` disables the line cap.
24
+ - `homeDirectory`: string
25
+ - `libraryList`: string[]
26
+ - `currentLibrary`: string
27
+ - `customVariables`: list of `{ name, value }`
28
+ - `sqlTransport`: `auto` (default), `mapepire` or `qzdfmdb2`. How SQL and SQL-run CL are executed; see "Persistent SQL job (Mapepire)" below.
29
+ - `mapepireMode`: `auto` (default), `daemon` or `ssh`.
30
+ - `mapepirePort`: number (default `8076`), the Mapepire server port for `daemon` mode.
31
+ - `mapepireServerPath`: path of `mapepire-server.jar` on the IBM i for `ssh` mode (default: `/QOpenSys/pkgs/lib/mapepire/mapepire-server.jar`, then `/opt/mapepire/lib/mapepire/mapepire-server.jar`).
32
+ - `mapepireJavaPath`: Java for `ssh` mode (default: the newest of jdk21, jdk17, jdk11, jdk80 under `/QOpenSys/QIBM/ProdData/JavaVM`).
33
+ - `allowedLocalRoots`: string[] (global settings only; see "Local file access" below)
34
+ - `tn5250Port`: number. TN5250 telnet port (default 23, or 992 when `tn5250Tls` is true).
35
+ - `tn5250Tls`: boolean. Use TLS for TN5250 (default true when the port is 992).
36
+ - `tn5250DeviceName`: string. Virtual device name requested for TN5250 sessions (1-10 characters).
37
+ - `tn5250LiveView`: boolean. Serve the live 5250 view on localhost while a session is open (default true).
38
+ - `tn5250LiveViewPort`: number. Preferred port for the live 5250 view (default 3981; the next free port when taken, 0 for any).
39
+ - `tn5250AttnWaitMs`: number. How long Attn/SysReq wait for the host before unlocking the keyboard, in milliseconds (default 2000, range 250-15000).
40
+ - `tn5250Ccsid`: number. Single-byte EBCDIC code page for TN5250 screen text (default 37; also 273, 277, 278, 280, 284, 285, 297, 500, 871, 1047, 1140-1149).
41
+
42
+ `tempLibrary` remains accepted for configuration compatibility. Source member reads and source-date writes now use job-local `QTEMP` objects and do not require a persistent `ILEDITOR` library.
43
+
44
+ ## Persistent SQL job (Mapepire)
45
+
46
+ By default every SQL statement (and every CL command run through SQL) starts a new QZDFMDB2 job over SSH, so a library list change, the current library, QTEMP objects and the job log are gone by the next call. When [Mapepire](https://mapepire-ibmi.github.io/) is installed on the IBM i, mcp-for-i keeps one database job per connection instead:
47
+
48
+ - The job is opened on the first SQL call of a session (not at connect time) and reused by `ibmi.sql.query`, `ibmi.sql.execute`, `ibmi.cl.run` (environment `ile`), `ibmi.joblog.get` and every other tool that runs SQL. Statements run one at a time in it.
49
+ - Before the first statement, the connection's `libraryList` and `currentLibrary` (from the active profile or settings) are applied with one `CHGLIBL LIBL(...) CURLIB(...)` (`CHGCURLIB` when the list is empty). It is applied again only when that setting changes, so a library list change made with `ibmi.cl.run` stays in effect until then.
50
+ - If a library in the setting does not exist (`CHGLIBL` then fails as a whole), the list is applied without it and `ibmi.session.status` shows `sqlJob.libraryListWarning` naming the missing libraries. SQL keeps working.
51
+ - Every CL command (`ibmi.cl.run`, `@` lines in SQL, a literal `CALL QSYS2.QCMDEXC('...')` statement, `ibmi.libl.*`) runs in this job, so its library list changes apply to later SQL and CL. Unqualified names resolve through the library list (system naming).
52
+ - `ibmi.libl.set`, `ibmi.libl.add`, `ibmi.libl.remove`, `ibmi.libl.setCurrent`, `ibmi.libl.move`, `ibmi.libl.prune`, `ibmi.libl.runProfileCommand` and `ibmi.profiles.activate` update the setting, so the next SQL or CL call uses the new list either way; `applyToJob: true` applies it immediately and returns any missing-library warning.
53
+ - `ibmi.joblog.get` reads this job's log (`QSYS2.JOBLOG_INFO('*')`), so it shows the messages of earlier calls. It leaves out `MCH3401 Cannot resolve to object PPTT`, which the database logs on every statement on some systems.
54
+ - Commitment control is off (`transaction isolation: none`), as with QZDFMDB2.
55
+ - When a `timeoutMs` is reached, the statement is cancelled on the IBM i with `QSYS2.CANCEL_SQL` (run from a separate QZDFMDB2 job). If it is still running 5 seconds later, the job is ended and a new one is opened on the next call (QTEMP and ad-hoc job changes are lost; the configured library list is applied again).
56
+ - If the job ends (network drop, server restart), the next call opens a new one. A read-only statement that was interrupted this way is retried once; a write is not retried.
57
+ - `ibmi.disconnect` and `ibmi.session.terminate` end the job. `ibmi.session.status` (field `sqlJob`) and `ibmi.session.bootstrap` (`transports.sqlJob`) show which transport is in use, the job name and the last error.
58
+ - Tools that run CL through a qsh `system` command (for example some source and object helpers) still run in their own job.
59
+
60
+ In `ssh` mode Mapepire first connects with its in-process CLI connection, which ignores the JDBC properties (so SQL naming) and runs each SQL request on its own thread; on IBM i the library list belongs to the thread, so a `CHGLIBL` run that way is lost when the request ends. mcp-for-i therefore switches the job to Mapepire's TCP technique right after it starts: a database host server job (`QZDASOINIT`, as in `daemon` mode) on the same system, signed on as the same user, where system naming applies and the library list persists. `sqlJob.technique` in `ibmi.session.status` shows `tcp`. This needs the database host server (`*DATABASE`, `STRHOSTSVR`) to be running. If the switch fails the CLI connection is kept (`technique: cli`): CL still keeps its library list changes because it is sent as a Mapepire CL request, which runs on the thread later SQL requests inherit from, but SQL naming applies, so unqualified table names resolve to the current schema instead of the library list.
61
+
62
+ Without the persistent job (QZDFMDB2, every call a new job) the configured library list is still applied: each SQL batch starts with `@CHGLIBL LIBL(...) CURLIB(...)` in the same QZDFMDB2 job, and CL that falls back to a qsh `system` command gets it through the qsh `liblist` builtin first. The list is checked once per setting value (one extra call); libraries that do not exist are left out and reported in `sqlJob.libraryListWarning`. QZDFMDB2 uses SQL naming, so there unqualified table names resolve to the current schema (normally the user profile name), not the library list; qualify them or use the Mapepire job.
63
+
64
+ `sqlTransport`:
65
+ - `auto` (default): use Mapepire when it can be reached, otherwise QZDFMDB2. The first failure is remembered for the session, so later calls go straight to QZDFMDB2 without waiting; reconnect to try again.
66
+ - `mapepire`: always use Mapepire; SQL fails with the reason when it cannot be opened.
67
+ - `qzdfmdb2`: never use Mapepire (the previous behaviour).
68
+
69
+ `mapepireMode`:
70
+ - `daemon`: connects to the Mapepire server (`mapepirePort`, default 8076) over TLS with the connection's user and password. Needs a password; key-only connections cannot use it.
71
+ - `ssh`: starts `mapepire-server.jar` in single-user mode over the connection's existing SSH session (`java -jar ... --single`). Works with key-based logins and needs no open port or running service, only the JAR and Java on the IBM i. Startup takes a few seconds (one JVM per session). Nothing is uploaded to the IBM i.
72
+ - `auto` (default): `daemon` when the connection has a password, falling back to `ssh` if the server is not reachable; `ssh` for key-only connections.
73
+
74
+ The daemon's server certificate is trusted on first use: Mapepire generates a self-signed certificate, so its fingerprint is stored in `known_hosts.json` (under `certificates`) the first time and must match afterwards (see [security](security.md)). Certificates from a public CA are checked the normal way. Certificate errors are never ignored.
75
+
76
+ ### Installing Mapepire on the IBM i
77
+
78
+ Mapepire is an open-source database server for IBM i. In short, from an SSH session with the IBM i open-source package manager (yum) installed, as a user allowed to install packages:
79
+
80
+ ```sh
81
+ yum install mapepire-server # installs /QOpenSys/pkgs/lib/mapepire/mapepire-server.jar
82
+ # daemon mode only: run it as a service with Service Commander
83
+ /QOpenSys/pkgs/bin/sc start mapepire
84
+ ```
85
+
86
+ The daemon listens on port 8076 and needs Java 11 or later. For `ssh` mode the package install is enough; no service has to run. See the Mapepire documentation for running it at IPL, certificates and any port or exit-point rules at your site.
87
+
88
+ ## Local file access
89
+
90
+ Tools that read or write files on your PC (`ibmi.ifs.upload`, `ibmi.ifs.download`, `ibmi.deploy.*`, `ibmi.audit.export` and `ibmi.compliance.report.generate`) only accept local paths inside the allowed local roots. Paths are resolved first: `..` and `~` are expanded and symlinks are followed, so a link that points outside a root is refused.
91
+
92
+ Allowed roots, first match wins:
93
+ 1. `MCP_FOR_I_ALLOWED_LOCAL_ROOTS`: a list of directories separated by the OS path delimiter (`;` on Windows, `:` elsewhere).
94
+ 2. `allowedLocalRoots` in the global `settings` of `config.json`.
95
+ 3. Default: the server's working directory and your home directory. A working directory that is a drive or filesystem root is ignored.
96
+
97
+ These locations are always refused, even inside an allowed root: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.azure`, `~/.kube`, `~/.docker`, `~/.config/gcloud`, `~/.config/gh`, any `credentials` file under `~/.config`, `~/.netrc`, `~/.git-credentials`, `~/.pgpass`, `~/.npmrc`, `~/.pypirc`, the mcp-for-i config directory, and on Windows `%APPDATA%` plus everything in `%LOCALAPPDATA%` except its `Temp` folder (macOS: `~/Library/Keychains`).
98
+
99
+ Example:
100
+ ```json
101
+ {
102
+ "settings": {
103
+ "allowedLocalRoots": ["C:/work/ibmi-projects", "D:/exports"]
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## IBM Documentation
109
+
110
+ Official documentation needs no credentials. The built-in catalog covers IBM i 7.4, 7.5, and 7.6 roots for the site map, manuals, RPG, CL, APIs, IBM i SQL services, security, database performance, and IFS commands. Optional environment settings:
111
+
112
+ - `MCP_FOR_I_IBM_DOCS_SEEDS`: JSON array of additional `{ title, url, release, categories, keywords }` entries. Every URL is still restricted to `https://www.ibm.com/docs/`.
113
+ - `MCP_FOR_I_IBM_DOCS_CACHE_DIR`: override the local documentation cache directory.
114
+ - `MCP_FOR_I_IBM_DOCS_CACHE_TTL_DAYS`: days before a cached documentation page is fetched again (default 7). An expired copy is still served, with a warning, when the refresh fails.
115
+
116
+ `ibmi.docs.search` is local/indexed by default. Use `freshness: "prefer-live"` for a bounded refresh of matching catalog entries. `ibmi.docs.open` uses cached content by default; use `freshness: "require-live"` when a live fetch is required.
117
+
118
+ ## Connections
119
+ Connections are stored without passwords. Passwords are stored in OS keychain (or in-session fallback). Each connection can include optional `settings` overrides.
120
+ The control plane health response and Overview page report whether password storage is persistent (`keychain`) or process-only (`session`).
121
+ WSL users should note that Linux Secret Service is separate from Windows Credential Manager; if WSL has no working keychain, passwords must be re-entered after the process exits.
122
+ Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage even when a keychain is available.
123
+ Set `MCP_FOR_I_CREDENTIAL_STORE=keychain` to require the OS keychain. In this mode saving or reading a password fails with a clear error when the keychain is unavailable, instead of silently keeping the password in memory where other processes (for example the MCP server) cannot see it. The default mode (`auto`) keeps the in-memory fallback.
124
+ Older config files may contain a plaintext `password` on a connection. It is moved to the keychain when the config is loaded and removed from the file only after the keychain confirms it was stored. Without a persistent keychain the plaintext stays in the file (a warning is printed to stderr) and is moved on a later run once a keychain is available.
125
+ Connections can also define policy, for example:
126
+ - `read-only`
127
+ - `guarded` (default; approvals required for risky operations)
128
+ - `power-user`
129
+
130
+ Policy lists (all optional, matched case-insensitively):
131
+ - `denyCommands` / `allowCommands`: CL command names. A library qualifier is ignored on both sides, so `DLTLIB` also blocks `QSYS/DLTLIB` and `*LIBL/DLTLIB`. A trailing `*` makes a generic entry (`DLT*`). When `allowCommands` is not empty, only listed commands may run, and QSH/PASE commands are refused because they cannot be verified. The lists apply to `ibmi.cl.run`, text on a recognised `===>` command line when `ibmi.tn5250.sendKeys` presses an AID key (other 5250 input cannot be checked; see [security](security.md)), the expanded command of `ibmi.actions.run`, and CL embedded in SQL (lines starting with `@` and `QCMDEXC` calls; a `QCMDEXC` call whose command is not a string literal is refused while a command list is set).
132
+ - `denyLibraries` / `allowLibraries`: library names, with optional trailing `*`. They apply to every tool argument that names a library (`library`, `journalLibrary`, `libraryList`, qualified `LIB/OBJ` values, `/QSYS.LIB/<lib>.LIB/...` paths). With `allowLibraries` set, special values such as `*LIBL` and generic names are refused because they cannot be checked.
133
+ - `denyPaths` / `allowPaths`: absolute IFS paths. An entry covers the path and everything below it; `*` matches within one path segment. With `allowPaths` set, relative paths are refused. Remote symlinks are not resolved.
134
+ - Deny entries always win over allow entries.
135
+
136
+ Approvals under `guarded`:
137
+ - `requireApprovalFor`: operations that ask the user first (default `["sql.write", "cl.run", "deploy.sync", "ifs.delete", "qsys.write"]`).
138
+ - `approvalMode`: `"prompt"` (default) asks the user through the MCP client and ignores the agent's `approve` flag. `"agent"` accepts `approve: true` from the agent instead, for clients that can't show prompts.
139
+ - The policy can only be changed in the web UI or in this file, not by the agent.
140
+
141
+ Example:
142
+ ```json
143
+ "policy": {
144
+ "profile": "guarded",
145
+ "denyCommands": ["DLTLIB", "CLRLIB", "PWRDWNSYS"],
146
+ "allowLibraries": ["DEV*", "QGPL", "QTEMP"],
147
+ "allowPaths": ["/home/devuser", "/tmp"]
148
+ }
149
+ ```
150
+
151
+ Example:
152
+ ```json
153
+ {
154
+ "connections": [
155
+ {
156
+ "name": "DEV400",
157
+ "host": "dev400.myco.com",
158
+ "port": 22,
159
+ "username": "DEVUSER",
160
+ "privateKeyPath": "C:/keys/id_rsa",
161
+ "settings": {
162
+ "readOnlyMode": false
163
+ },
164
+ "policy": {
165
+ "profile": "guarded",
166
+ "requireApprovalFor": ["sql.write", "cl.run", "deploy.sync"]
167
+ },
168
+ "profiles": [
169
+ {
170
+ "name": "projectA",
171
+ "currentLibrary": "PROJALIB",
172
+ "libraryList": ["QSYS", "QGPL", "PROJALIB"],
173
+ "customVariables": [{ "name": "ENV", "value": "DEV" }]
174
+ }
175
+ ],
176
+ "currentProfile": "projectA"
177
+ }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ## Control plane
183
+
184
+ - `MCP_FOR_I_CONTROL_HOST` (default `127.0.0.1`) and `MCP_FOR_I_CONTROL_PORT` (default `3980`) set where the control plane listens. The background-service launchers record the port configured when `mcp-for-i control enable` ran; the Bash launcher also honours `MCP_FOR_I_CONTROL_PORT` at start-up.
185
+ - The API token lives in `controlplane.token` in the same directory as `config.json`. See [security](security.md#control-plane).
186
+
187
+ ## Audit log files
188
+ The audit log lives in `<configDir>/audit/` (or next to `MCP_FOR_I_AUDIT_LOG_PATH` when set):
189
+ - `tool-audit.jsonl`: one record per tool call.
190
+ - `tool-audit.key`: per-install secret used to sign records (HMAC-SHA256), created on first use with owner-only permissions. Set `MCP_FOR_I_AUDIT_KEY_PATH` to keep it elsewhere. Losing or replacing it makes existing records unverifiable.
191
+ - `tool-audit.anchor.json`: signed checkpoint with the first record hash, the last sequence number and the last hash, updated on every append and purge.
192
+ - `tool-audit.head`: last record hash (kept for older versions).
193
+ - `tool-audit.lock`: held briefly while appending, purging or verifying, so several MCP processes write one chain.
package/docs/examples.md CHANGED
@@ -1,73 +1,109 @@
1
- # Examples
2
-
3
- ## Add connection profile
4
- ```json
5
- {
6
- "tool": "ibmi.connections.add",
7
- "arguments": {
8
- "name": "DEV400",
9
- "host": "dev400.myco.com",
10
- "port": 22,
11
- "username": "DEVUSER",
12
- "password": "*****",
13
- "storePassword": true,
14
- "policy": { "profile": "guarded" }
15
- }
16
- }
17
- ```
18
-
19
- ## Connect by saved name
20
- ```json
21
- {
22
- "tool": "ibmi.connect",
23
- "arguments": { "name": "DEV400" }
24
- }
25
- ```
26
-
27
- ## List libraries
28
- ```json
29
- {
30
- "tool": "ibmi.qsys.libraries.list",
31
- "arguments": { "filter": "*ALL" }
32
- }
33
- ```
34
-
35
- ## Read a member
36
- ```json
37
- {
38
- "tool": "ibmi.qsys.members.read",
39
- "arguments": {
40
- "library": "MYLIB",
41
- "sourceFile": "QRPGLESRC",
42
- "member": "MYPGM"
43
- }
44
- }
45
- ```
46
-
47
- ## Run an action
48
- ```json
49
- {
50
- "tool": "ibmi.actions.run",
51
- "arguments": {
52
- "actionName": "Create RPGLE Program",
53
- "targetType": "member",
54
- "targetPath": "MYLIB/QRPGLESRC/MYPGM",
55
- "library": "MYLIB",
56
- "sourceFile": "QRPGLESRC",
57
- "member": "MYPGM",
58
- "extension": "RPGLE"
59
- }
60
- }
61
- ```
62
-
63
- ## Activate a profile
64
- ```json
65
- {
66
- "tool": "ibmi.profiles.activate",
67
- "arguments": {
68
- "connectionName": "DEV400",
69
- "name": "projectA",
70
- "applyToJob": true
71
- }
72
- }
73
- ```
1
+ # Examples
2
+
3
+ ## Add connection profile
4
+ ```json
5
+ {
6
+ "tool": "ibmi.connections.add",
7
+ "arguments": {
8
+ "name": "DEV400",
9
+ "host": "dev400.myco.com",
10
+ "port": 22,
11
+ "username": "DEVUSER",
12
+ "policy": { "profile": "guarded" }
13
+ }
14
+ }
15
+ ```
16
+
17
+ Set or update the password through the local control-plane UI so it can be stored in the operating-system credential store; MCP tool arguments do not accept secrets.
18
+
19
+ ## Connect by saved name
20
+ ```json
21
+ {
22
+ "tool": "ibmi.connect",
23
+ "arguments": { "name": "DEV400" }
24
+ }
25
+ ```
26
+
27
+ ## List libraries
28
+ ```json
29
+ {
30
+ "tool": "ibmi.qsys.libraries.list",
31
+ "arguments": { "filter": "*ALL" }
32
+ }
33
+ ```
34
+
35
+ ## Read a member
36
+ ```json
37
+ {
38
+ "tool": "ibmi.qsys.members.read",
39
+ "arguments": {
40
+ "library": "MYLIB",
41
+ "sourceFile": "QRPGLESRC",
42
+ "member": "MYPGM"
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Inspect a member in one versioned operation
48
+
49
+ ```json
50
+ {
51
+ "tool": "ibmi.source.inspect",
52
+ "arguments": {
53
+ "connectionName": "DEV400",
54
+ "library": "MYLIB",
55
+ "member": "MYPGM",
56
+ "candidateSourceFiles": ["QRPGLESRC", "QCLSRC"],
57
+ "operations": [
58
+ { "operation": "metadata" },
59
+ { "operation": "outline" },
60
+ { "operation": "search", "terms": ["PCFCID", "CHAIN"], "before": 2, "after": 3 },
61
+ { "operation": "read_ranges", "ranges": [{ "start": 120, "end": 180 }] },
62
+ { "operation": "dependencies" }
63
+ ]
64
+ }
65
+ }
66
+ ```
67
+
68
+ Use the returned `version` or `sha256` as `expectedVersion` / `expectedSha256` on a later structured read or member write.
69
+
70
+ ## Search official IBM i documentation
71
+
72
+ ```json
73
+ {
74
+ "tool": "ibmi.docs.search",
75
+ "arguments": {
76
+ "query": "QSYS2 SQL services HTTP functions",
77
+ "connectionName": "DEV400",
78
+ "limit": 5
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Run an action
84
+ ```json
85
+ {
86
+ "tool": "ibmi.actions.run",
87
+ "arguments": {
88
+ "actionName": "Create RPGLE Program",
89
+ "targetType": "member",
90
+ "targetPath": "MYLIB/QRPGLESRC/MYPGM",
91
+ "library": "MYLIB",
92
+ "sourceFile": "QRPGLESRC",
93
+ "member": "MYPGM",
94
+ "extension": "RPGLE"
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Activate a profile
100
+ ```json
101
+ {
102
+ "tool": "ibmi.profiles.activate",
103
+ "arguments": {
104
+ "connectionName": "DEV400",
105
+ "name": "projectA",
106
+ "applyToJob": true
107
+ }
108
+ }
109
+ ```
package/docs/overview.md CHANGED
@@ -1,15 +1,15 @@
1
- # MCP-for-i Overview
2
-
3
- MCP-for-i is a local MCP server and control plane that exposes IBM i operations as MCP tools for agent-driven workflows (Codex CLI/Desktop and other MCP clients).
4
-
5
- Current platform scope includes:
6
- - Secure onboarding and connection lifecycle management.
7
- - Guarded policy and approval-gated operations.
8
- - Session lifecycle controls with keepalive/reconnect behavior.
9
- - QSYS, IFS, search, deploy, SQL/CL, diagnostics, and TN5250 command-session tooling.
10
- - Audit-chain integrity features plus journaling and compliance-report tooling.
11
- - IBM i operations-control tooling for spool/job/subsystem/message-queue lifecycles, lock/authority visibility, and data queue/data area operations.
12
-
13
- For the complete up-to-date feature inventory, see:
14
- - `docs/capabilities.md`
15
- - `docs/tools.md`
1
+ # MCP-for-i Overview
2
+
3
+ MCP-for-i is a local MCP server and control plane that exposes IBM i operations as MCP tools for agent-driven workflows (Codex CLI/Desktop and other MCP clients).
4
+
5
+ Current platform scope includes:
6
+ - Secure onboarding and connection lifecycle management.
7
+ - Guarded policy and approval-gated operations.
8
+ - Session lifecycle controls with keepalive/reconnect behavior.
9
+ - QSYS, IFS, search, deploy, SQL/CL, diagnostics, and TN5250 (5250 green-screen) sessions.
10
+ - Audit-chain integrity features plus journaling and compliance-report tooling.
11
+ - IBM i operations-control tooling for spool/job/subsystem/message-queue lifecycles, lock/authority visibility, and data queue/data area operations.
12
+
13
+ For the complete up-to-date feature inventory, see:
14
+ - `docs/capabilities.md`
15
+ - `docs/tools.md`
package/docs/security.md CHANGED
@@ -1,16 +1,101 @@
1
- # Security
2
-
3
- - Credentials are stored in OS keychain via `keytar` (Windows Credential Manager, macOS Keychain, Linux libsecret).
4
- - Credential storage is selected at runtime from the Node process environment:
5
- - Windows Node from PowerShell, `cmd`, or Git Bash uses Windows Credential Manager.
6
- - macOS Node uses macOS Keychain.
7
- - Linux Node uses Linux Secret Service.
8
- - WSL Node is Linux and uses the WSL distro's Linux Secret Service, not Windows Credential Manager.
9
- - Keychain availability is checked as an actual read operation, not only as a module import.
10
- - If keychain is unavailable or a keychain operation fails, credentials are stored in memory for the current process only.
11
- - In WSL, `keytar` uses the Linux Secret Service inside the distro. It does not automatically use Windows Credential Manager. For persistent credentials in WSL, configure Linux libsecret/Secret Service, use SSH keys, or run MCP-for-i from Windows.
12
- - Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage.
13
- - Passwords are never written to the config file.
14
- - `readOnlyMode` prevents write operations on members/IFS.
15
- - Connection policies support `read-only`, `guarded`, and `power-user` modes.
16
- - Guarded mode requires `approve: true` for configured risky operations.
1
+ # Security
2
+
3
+ - Credentials are stored in OS keychain via `keytar` (Windows Credential Manager, macOS Keychain, Linux libsecret).
4
+ - Credential storage is selected at runtime from the Node process environment:
5
+ - Windows Node from PowerShell, `cmd`, or Git Bash uses Windows Credential Manager.
6
+ - macOS Node uses macOS Keychain.
7
+ - Linux Node uses Linux Secret Service.
8
+ - WSL Node is Linux and uses the WSL distro's Linux Secret Service, not Windows Credential Manager.
9
+ - Keychain availability is checked as an actual read operation, not only as a module import.
10
+ - In the default `auto` mode, if the keychain is unavailable or a keychain operation fails, credentials are stored in memory for the current process only.
11
+ - With `MCP_FOR_I_CREDENTIAL_STORE=keychain`, there is no in-memory fallback: saving, reading or deleting a password fails with an error when the keychain is unavailable.
12
+ - In WSL, `keytar` uses the Linux Secret Service inside the distro. It does not automatically use Windows Credential Manager. For persistent credentials in WSL, configure Linux libsecret/Secret Service, use SSH keys, or run MCP-for-i from Windows.
13
+ - Set `MCP_FOR_I_CREDENTIAL_STORE=session` to force process-only password storage.
14
+ - Passwords are never written to the config file. Plaintext passwords found in older config files are moved to the keychain and removed from the file only after the keychain confirms the save; without a persistent keychain they are left in place (with a warning) rather than lost.
15
+ - The config file is written atomically with owner-only permissions, never overwritten after a failed read, and updated under a cross-process lock.
16
+ - `readOnlyMode` refuses every tool that changes the system: member and IFS writes, `ibmi.sql.execute`, `ibmi.cl.run`, every AID key of `ibmi.tn5250.sendKeys` (and `ibmi.tn5250.connect` with `signOn`), `ibmi.actions.run`, and destructive data-queue receives.
17
+ - Connection policies support `read-only`, `guarded`, and `power-user` modes.
18
+ - Guarded mode asks the user before the operations listed in `requireApprovalFor` run. The prompt is shown by the MCP client (MCP elicitation) and lists the tool, the connection and the arguments that decide what runs. The agent's own `approve: true` counts for nothing here. The user can decline, accept once, or accept that kind of operation on that connection until the server restarts.
19
+ - If the client cannot show prompts, guarded operations are refused. To use such a client, set the connection's `policy.approvalMode` to `"agent"` in config.json; `approve: true` from the agent is then accepted, as in earlier versions.
20
+ - Under guarded, these always ask the user, whatever `requireApprovalFor` lists, and are never covered by an "until restart" answer: shell commands (`ibmi.cl.run` with `qsh`/`pase`, CL that starts a shell such as `QSH`, `STRQSH` or `CALL QP2SHELL`, SQL whose `QCMDEXC` starts a shell or can't be read, and `ibmi.actions.run` actions that run shell text), library deletes, `ibmi.audit.purge`, and journal receiver deletes. IFS deletes ask whenever `ifs.delete` is listed (it is by default) and are also never covered by an "until restart" answer.
21
+ - The agent cannot change a connection's policy or its safety settings (`readOnlyMode`, `tempDir`, `tempLibrary`, `autoClearTempData`, `mapepire*`) through `ibmi.connections.add`/`update`. Those are changed in the web UI or in config.json.
22
+ - Command, library and IFS path allow/deny lists are enforced. CL command names are normalized (trimmed, uppercased, library qualifier removed) before matching, and the command lists also apply to the expanded command of `ibmi.actions.run` and to CL embedded in SQL (`@` lines and `QCMDEXC`). See [configuration](configuration.md) for the exact rules.
23
+ - `ibmi.tn5250.*` is a real 5250 terminal session. A 5250 screen does not say what an Enter key will do: it might run a CL command, update a file in an application, or sign off. So the controls are coarse:
24
+ - `readOnlyMode` (and the `read-only` policy) blocks sending any AID key at all (Enter, F-keys, PageUp/PageDown, Clear, Help, Attn, SysReq) and `signOn`. A read-only connection can only open a session and read the first screen.
25
+ - `denyCommands` / `allowCommands` only apply to text on a recognised command line (an input field labelled `===>`, as on IBM i menus and the command entry display). That text is checked as `cl.run`, which also asks the user under the default guarded policy. Commands reached any other way (menu options, prompt screens, application programs, F-keys) are not checked. Treat the policy as a guard against obvious mistakes, not a sandbox; use IBM i object authority and a limited user profile for real restrictions.
26
+ - Other AID keys are checked as the `tn5250.sendKeys` operation; add it to `requireApprovalFor` to ask the user before every key press. Keys typed into a shell that is already open on the screen are not seen as shell commands.
27
+ - TN5250 passwords come only from the connection's stored password (`signOn`, or `setField` with `useStoredPassword`). Over TLS the password is offered with RFC 4777 auto sign-on (plain-text password inside the TLS channel); on plain telnet it is typed into the sign-on screen and travels unencrypted, as with any 5250 emulator. Use port 992 (TLS) wherever possible.
28
+ - Anything typed into a hidden (non-display) 5250 field is redacted from the log and the audit record, and readScreen/snapshot never return hidden field contents.
29
+
30
+ ## Control plane
31
+
32
+ The local control plane (`mcp-for-i control`) only accepts requests from its own Web UI and CLI:
33
+
34
+ - A random per-install token is created on first start and stored next to the config file as `controlplane.token` (owner-only file mode where the platform supports it). Every `/api` call must send it in the `X-MCP-For-I-Token` header. The Web UI gets it in the page it is served; the CLI reads it from the file. Delete the file and restart the control plane to rotate it.
35
+ - `GET /api/health` answers without the token so launchers can check liveness, but it only returns `{ ok, service }` unless the token is sent.
36
+ - The `Host` header must be `localhost`, `127.0.0.1` or `[::1]` with the control-plane port (blocks DNS rebinding), and a cross-origin `Origin` header is rejected.
37
+ - Request bodies must be `application/json` and at most 1 MB (larger bodies get `413`).
38
+ - Changing a saved connection's host, port or username requires entering its password again in the same request, so a stored password cannot be redirected to another server. Adding a connection never overwrites an existing one.
39
+ - Skills updates only accept `https://`, `ssh://` or `user@host:path` repository URLs and branch names that follow git's ref-name rules.
40
+ - Update and service jobs return `202` right away; poll `GET /api/runtime/status` for progress.
41
+
42
+ ## SSH host key verification
43
+
44
+ - The first time a host is connected, its SSH host key fingerprint (OpenSSH `SHA256:...` format) is stored per `host:port` in `known_hosts.json` in the mcp-for-i config directory (the folder that holds `config.json`: `%APPDATA%\mcp-for-i` on Windows, `~/Library/Application Support/mcp-for-i` on macOS, `$XDG_CONFIG_HOME/mcp-for-i` or `~/.config/mcp-for-i` on Linux).
45
+ - On every later connection the presented key must match. If it does not, the connection is refused with an error showing the stored and presented fingerprints. This can mean a man-in-the-middle attack.
46
+ - To accept a key change that you expect (system rebuilt, SSH keys regenerated), compare the new fingerprint with the one on the IBM i (run `ssh-keygen -lf` on the host key `.pub` file in the OpenSSH `etc` directory there), then either:
47
+ - set `MCP_FOR_I_ACCEPT_NEW_HOSTKEY` for one start of the server and connect again, then unset it. Accepted values, comma separated: `host:port` or `host` (that host only), the new `SHA256:...` fingerprint (only that key), or `1`/`true` (any host; avoid this); or
48
+ - remove the host's entry from `known_hosts.json`; the next connection trusts the key it sees.
49
+ - A damaged `known_hosts.json` is never silently reset; connections fail until it is fixed or removed.
50
+
51
+ ## Mapepire TLS certificate
52
+
53
+ - In `daemon` mode the persistent SQL job connects to the Mapepire server over TLS. Before any credentials are sent, the server's certificate chain is read. If it ends in a publicly trusted CA, the normal CA and host-name checks apply.
54
+ - Otherwise (Mapepire's default self-signed certificate, or a private CA) the root certificate's `SHA256:...` fingerprint is stored per `host:port` under `certificates` in `known_hosts.json` on first use. Every later connection must present a chain that validates against that certificate and contains it; the host name is not compared, because the generated certificate rarely matches the name used to connect.
55
+ - A changed certificate is refused like a changed SSH host key. Accept an expected change with `MCP_FOR_I_ACCEPT_NEW_HOSTKEY` (`host:port` of the Mapepire port, `host`, or the new fingerprint) or by removing the entry. With `sqlTransport: auto` a refused certificate makes SQL fall back to QZDFMDB2 over SSH; the reason is shown in `ibmi.session.status` (`sqlJob.unavailableReason`).
56
+ - `ssh` mode runs over the already verified SSH session and uses no TLS.
57
+
58
+ ## TN5250 TLS certificates
59
+
60
+ - A TN5250 TLS certificate that chains to a trusted CA and matches the host name is accepted.
61
+ - Any other certificate (typically the self-signed certificate of the IBM i Telnet server) is trusted on first use: its SHA-256 fingerprint is pinned per `host:port` in `known_tls_certs.json` in the mcp-for-i config directory, and a different certificate later is refused.
62
+ - To accept an expected change (certificate renewed), set `MCP_FOR_I_ACCEPT_NEW_TLSCERT` the same way as `MCP_FOR_I_ACCEPT_NEW_HOSTKEY` (`host:port`, `host`, the new `SHA256:...` fingerprint, or `1`), or remove the entry from `known_tls_certs.json`.
63
+
64
+ ## SQL handling
65
+
66
+ - SQL text is sent to QZDFMDB2 on the SSH channel's standard input. It is never placed inside the shell command line. With the persistent Mapepire job it is sent as a protocol message, and CL commands run through `QSYS2.QCMDEXC` with the command as a bound parameter.
67
+ - The read-only and policy checks below run before SQL reaches either transport.
68
+ - `ibmi.sql.query` accepts exactly one `SELECT`, `WITH` or `VALUES` statement (an optional trailing `;` is allowed). It rejects:
69
+ - more than one statement;
70
+ - lines starting with `@` (these run CL commands through QCMDEXC);
71
+ - any reference to `QCMDEXC`;
72
+ - data-change table references such as `FINAL TABLE (INSERT ...)`, `NEW TABLE (UPDATE ...)` and `OLD TABLE (DELETE ...)`;
73
+ - unterminated strings or comments, and a `;` inside a string or comment (QZDFMDB2 splits statements on `;` itself).
74
+ - The check is lexical. A `SELECT` can still call a user-defined function that changes data; use object authority and a read-only connection profile to restrict that.
75
+
76
+ ## Local file access
77
+
78
+ Tools that touch files on your PC only accept paths inside the allowed local roots (default: the working directory and your home directory). Paths are fully resolved, including `..` and symlinks, before the check. Credential stores such as `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/*/credentials`, the mcp-for-i config directory and (on Windows) `%APPDATA%` are always refused. Set `MCP_FOR_I_ALLOWED_LOCAL_ROOTS` or `settings.allowedLocalRoots` to narrow or change the roots. Files inside a directory passed to `ibmi.deploy.uploadDirectory` are uploaded as they are found; keep symlinks to outside locations out of deploy folders.
79
+
80
+ ## IFS deletes
81
+
82
+ `ibmi.ifs.delete` refuses empty or relative paths, paths containing `..` or wildcards, `/`, any top-level directory, `/QSYS.LIB/<lib>.LIB`, second-level directories under `/QOpenSys` and `/QIBM`, and IBM product and system library locations.
83
+
84
+ It also refuses user home directories (`/home/<user>`). A recursive delete must name a path at least three levels deep (for example `/home/USER/work`) and repeat it in `confirmPath`, because `rm -rf` on a home or project folder cannot be undone.
85
+
86
+ ## Shell quoting
87
+
88
+ Action commands in the `ile` environment are passed to `system` as a single-quoted shell word, so quotes, backticks and `$` in a command or target path cannot start another shell command. Streamfile action targets must be absolute IFS paths without quotes, backticks, `$`, backslashes, `;`, `|`, `&`, `<`, `>` or control characters.
89
+
90
+ ## Secret redaction
91
+
92
+ Logs and audit records redact secrets by key name (case-insensitive substring match on `password`, `passwd`, `pwd`, `secret`, `token`, `apikey`, `privatekey`, `passphrase`, `authorization` and similar) and inside values: CL parameters such as `PASSWORD(...)`, `PWD(...)`, `NEWPWD(...)` and `CURPWD(...)`, SQL `PASSWORD '...'`, `SET ENCRYPTION PASSWORD` and `USING '...'`, command-line flags like `--password=...`, `key=value` pairs and `Bearer` tokens.
93
+
94
+ ## Audit log integrity
95
+ - Every tool call is appended to a hash chain. New records (`version: 2`) carry a sequence number and are signed with HMAC-SHA256 using a per-install key (`tool-audit.key`, owner-only permissions), so the chain cannot be recomputed without the key.
96
+ - A signed anchor file records the first record hash, the last sequence number and the last hash. `ibmi.audit.verify` uses it to detect records removed from the start or the end of the log, sequence gaps, edited records, and a modified or missing anchor. If the anchor has to be rebuilt, the rebuild is recorded and keeps being reported by verify.
97
+ - Records written by older versions (`version: 1`, unkeyed) are verified with the old algorithm and reported as a `legacy` segment. An unkeyed record after keyed records is reported as a failure.
98
+ - Appends, purges and verification take a lock file, and the previous hash is read from the log under that lock, so several MCP processes extend one chain. A failed append is reported to the caller and does not stop later appends.
99
+ - Purges run inside the same lock and keep the remaining records unchanged; the anchor is moved to the new first record.
100
+ - `ibmi.audit.list` and `ibmi.audit.export` skip unparseable lines and return their count as `skippedLines`; verify reports them as failures.
101
+ - The key file protects against edits by anyone who cannot read it. A local user or process with access to the key can still rewrite the log; copy exports to separate storage for stronger guarantees.