mcp-for-i 0.1.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.
@@ -0,0 +1,74 @@
1
+ export function validateToolInput(schema, value) {
2
+ const errors = [];
3
+ validateNode(schema, value, "$", errors);
4
+ return errors;
5
+ }
6
+ function validateNode(schema, value, path, errors) {
7
+ if (!matchesType(schema.type, value)) {
8
+ const typeText = Array.isArray(schema.type) ? schema.type.join("|") : schema.type || "unknown";
9
+ errors.push(`${path} must be ${typeText}`);
10
+ return;
11
+ }
12
+ if (schema.enum && !schema.enum.includes(value)) {
13
+ errors.push(`${path} must be one of: ${schema.enum.map(v => JSON.stringify(v)).join(", ")}`);
14
+ return;
15
+ }
16
+ if (isObjectSchema(schema, value)) {
17
+ const obj = value;
18
+ for (const key of schema.required || []) {
19
+ if (!(key in obj) || obj[key] === undefined) {
20
+ errors.push(`${path}.${key} is required`);
21
+ }
22
+ }
23
+ if (schema.additionalProperties === false && schema.properties) {
24
+ for (const key of Object.keys(obj)) {
25
+ if (!(key in schema.properties)) {
26
+ errors.push(`${path}.${key} is not allowed`);
27
+ }
28
+ }
29
+ }
30
+ if (schema.properties) {
31
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
32
+ if (obj[key] !== undefined) {
33
+ validateNode(propSchema, obj[key], `${path}.${key}`, errors);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ if (Array.isArray(value) && schema.items) {
39
+ value.forEach((entry, idx) => validateNode(schema.items, entry, `${path}[${idx}]`, errors));
40
+ }
41
+ }
42
+ function isObjectSchema(schema, value) {
43
+ const types = toTypes(schema.type);
44
+ return types.includes("object") && isPlainObject(value);
45
+ }
46
+ function matchesType(type, value) {
47
+ if (!type)
48
+ return true;
49
+ const accepted = toTypes(type);
50
+ return accepted.some(t => isType(t, value));
51
+ }
52
+ function toTypes(type) {
53
+ if (!type)
54
+ return [];
55
+ return Array.isArray(type) ? type : [type];
56
+ }
57
+ function isType(type, value) {
58
+ if (type === "string")
59
+ return typeof value === "string";
60
+ if (type === "number")
61
+ return typeof value === "number" && Number.isFinite(value);
62
+ if (type === "boolean")
63
+ return typeof value === "boolean";
64
+ if (type === "array")
65
+ return Array.isArray(value);
66
+ if (type === "object")
67
+ return isPlainObject(value);
68
+ if (type === "null")
69
+ return value === null;
70
+ return true;
71
+ }
72
+ function isPlainObject(value) {
73
+ return typeof value === "object" && value !== null && !Array.isArray(value);
74
+ }
@@ -0,0 +1,42 @@
1
+ const SERVICE = "mcp-for-i";
2
+ const sessionStore = new Map();
3
+ let keytar;
4
+ async function getKeytar() {
5
+ if (keytar !== undefined)
6
+ return keytar;
7
+ try {
8
+ const mod = await import("keytar");
9
+ keytar = mod.default || mod;
10
+ }
11
+ catch {
12
+ keytar = null;
13
+ }
14
+ return keytar;
15
+ }
16
+ export async function setPassword(connectionName, password) {
17
+ const kt = await getKeytar();
18
+ if (kt) {
19
+ await kt.setPassword(SERVICE, connectionName, password);
20
+ }
21
+ else {
22
+ sessionStore.set(connectionName, password);
23
+ }
24
+ }
25
+ export async function getPassword(connectionName) {
26
+ const kt = await getKeytar();
27
+ if (kt) {
28
+ return kt.getPassword(SERVICE, connectionName);
29
+ }
30
+ return sessionStore.get(connectionName);
31
+ }
32
+ export async function deletePassword(connectionName) {
33
+ const kt = await getKeytar();
34
+ if (kt) {
35
+ await kt.deletePassword(SERVICE, connectionName);
36
+ }
37
+ sessionStore.delete(connectionName);
38
+ }
39
+ export async function isKeychainAvailable() {
40
+ const kt = await getKeytar();
41
+ return Boolean(kt);
42
+ }
@@ -0,0 +1,57 @@
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
+ Connections can also define policy, for example:
24
+ - `read-only`
25
+ - `guarded` (default; approvals required for risky operations)
26
+ - `power-user`
27
+
28
+ Example:
29
+ ```json
30
+ {
31
+ "connections": [
32
+ {
33
+ "name": "DEV400",
34
+ "host": "dev400.myco.com",
35
+ "port": 22,
36
+ "username": "DEVUSER",
37
+ "privateKeyPath": "C:/keys/id_rsa",
38
+ "settings": {
39
+ "readOnlyMode": false
40
+ },
41
+ "policy": {
42
+ "profile": "guarded",
43
+ "requireApprovalFor": ["sql.write", "cl.run", "deploy.sync"]
44
+ },
45
+ "profiles": [
46
+ {
47
+ "name": "projectA",
48
+ "currentLibrary": "PROJALIB",
49
+ "libraryList": ["QSYS", "QGPL", "PROJALIB"],
50
+ "customVariables": [{ "name": "ENV", "value": "DEV" }]
51
+ }
52
+ ],
53
+ "currentProfile": "projectA"
54
+ }
55
+ ]
56
+ }
57
+ ```
@@ -0,0 +1,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
+ "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
+ ```
@@ -0,0 +1,61 @@
1
+ # Installation
2
+
3
+ ## Prerequisites
4
+ - Node.js 18+ recommended
5
+ - IBM i SSH access (22)
6
+ - On Linux: `libsecret` (for keychain via `keytar`)
7
+
8
+ ## Install
9
+ 1. Clone or copy this repo
10
+ 2. Install dependencies:
11
+ - `npm install`
12
+ 3. Build:
13
+ - `npm run build`
14
+ 4. Run:
15
+ - `node dist/index.js`
16
+
17
+ ## Control Plane UI (Onboarding + Updates)
18
+ Run local control plane:
19
+
20
+ - `npm run start:controlplane`
21
+
22
+ Open:
23
+
24
+ - `http://127.0.0.1:3980`
25
+
26
+ Use the UI to add/edit/delete/rename connections, manage keychain passwords, and run MCP/skills update actions.
27
+
28
+ ## MCP (stdio) usage
29
+ Configure your MCP client (e.g., Codex CLI) to run `mcp-for-i` via stdio. Example (pseudo-config):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "mcp-for-i": {
35
+ "command": "mcp-for-i"
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ If you run from source, use:
42
+
43
+ ```json
44
+ {
45
+ "command": "node",
46
+ "args": ["C:/Users/pgmashish/projects/mcp/mcp-for-i/dist/index.js"]
47
+ }
48
+ ```
49
+
50
+ ## Logging (opt‑in)
51
+ Logging is **disabled by default**. Enable it via env vars or CLI flags:
52
+
53
+ - Env flags:
54
+ - `MCP_FOR_I_LOG_ENABLED=1`
55
+ - `MCP_FOR_I_LOG` = path to log file
56
+ - `MCP_FOR_I_LOG_LEVEL` = `error|warn|info|debug` (default `info`)
57
+ - `MCP_FOR_I_LOG_STDERR=1` to log to stderr (set `0` to disable)
58
+ - CLI flags (when running `node dist/index.js` directly):
59
+ - `--log` (stderr, info level)
60
+ - `--log-level=debug`
61
+ - `--log-file=C:/path/to/log.txt`
@@ -0,0 +1,14 @@
1
+ # MCP-for-i Overview
2
+
3
+ MCP-for-i is a local MCP server that exposes IBM i operations as MCP tools. It mirrors the core capabilities of the Code for IBM i VS Code extension but is designed for agent-driven workflows (e.g., Codex CLI) over MCP stdio.
4
+
5
+ Key capabilities (Phase 1):
6
+ - Connect/disconnect to IBM i
7
+ - Keep multiple sessions warm with inactivity timeout
8
+ - Browse libraries/objects/members
9
+ - Read/write members and IFS files
10
+ - Run compile actions (RPG/COBOL/C/CL/SQL)
11
+ - Search members and IFS
12
+ - Run guarded SQL and CL tools
13
+
14
+ The server runs locally and connects to IBM i via SSH + QSH/PASE + SQL (QZDFMDB2).
@@ -0,0 +1,8 @@
1
+ # Security
2
+
3
+ - Credentials are stored in OS keychain via `keytar` (Windows Credential Manager, macOS Keychain, Linux libsecret).
4
+ - If keychain is unavailable, credentials are stored in memory for the current session only.
5
+ - Passwords are never written to the config file.
6
+ - `readOnlyMode` prevents write operations on members/IFS.
7
+ - Connection policies support `read-only`, `guarded`, and `power-user` modes.
8
+ - Guarded mode requires `approve: true` for configured risky operations.
package/docs/tools.md ADDED
@@ -0,0 +1,105 @@
1
+ # Tools
2
+
3
+ ## Connection
4
+ - `ibmi.connect`
5
+ - `ibmi.disconnect`
6
+ - `ibmi.session.list`
7
+ - `ibmi.session.status`
8
+ - `ibmi.session.keepalive`
9
+ - `ibmi.session.terminate`
10
+ - `ibmi.connections.list`
11
+ - `ibmi.connections.add`
12
+ - `ibmi.connections.update`
13
+ - `ibmi.connections.delete`
14
+
15
+ ## QSYS / Members
16
+ - `ibmi.qsys.libraries.list`
17
+ - `ibmi.qsys.objects.list`
18
+ - `ibmi.qsys.sourcefiles.list`
19
+ - `ibmi.qsys.members.list`
20
+ - `ibmi.qsys.members.read`
21
+ - `ibmi.qsys.members.write`
22
+ - `ibmi.qsys.members.create`
23
+ - `ibmi.qsys.members.rename`
24
+ - `ibmi.qsys.members.delete`
25
+ - `ibmi.qsys.sourcefiles.create`
26
+ - `ibmi.qsys.libraries.create`
27
+
28
+ ## IFS
29
+ - `ibmi.ifs.list`
30
+ - `ibmi.ifs.read`
31
+ - `ibmi.ifs.write`
32
+ - `ibmi.ifs.mkdir`
33
+ - `ibmi.ifs.delete`
34
+ - `ibmi.ifs.upload`
35
+ - `ibmi.ifs.download`
36
+
37
+ ## Search
38
+ - `ibmi.search.members`
39
+ - `ibmi.search.ifs`
40
+ - `ibmi.find.ifs`
41
+
42
+ ## Actions
43
+ - `ibmi.actions.list`
44
+ - `ibmi.actions.run`
45
+ - `ibmi.actions.save`
46
+ - `ibmi.actions.delete`
47
+
48
+ ## Library List
49
+ - `ibmi.libl.get`
50
+ - `ibmi.libl.set`
51
+ - `ibmi.libl.add`
52
+ - `ibmi.libl.remove`
53
+ - `ibmi.libl.setCurrent`
54
+ - `ibmi.libl.validate`
55
+
56
+ ## Profiles
57
+ - `ibmi.profiles.list`
58
+ - `ibmi.profiles.save`
59
+ - `ibmi.profiles.delete`
60
+ - `ibmi.profiles.activate`
61
+
62
+ ## Resolve
63
+ - `ibmi.resolve.path`
64
+
65
+ ## Deploy
66
+ - `ibmi.deploy.uploadDirectory`
67
+ - `ibmi.deploy.uploadFiles`
68
+ - `ibmi.deploy.setCcsid`
69
+ - `ibmi.deploy.compare`
70
+ - `ibmi.deploy.sync`
71
+
72
+ ## Filters
73
+ - `ibmi.filters.list`
74
+ - `ibmi.filters.save`
75
+ - `ibmi.filters.delete`
76
+
77
+ ## IFS Shortcuts
78
+ - `ibmi.ifs.shortcuts.list`
79
+ - `ibmi.ifs.shortcuts.add`
80
+ - `ibmi.ifs.shortcuts.delete`
81
+
82
+ ## Debug
83
+ - `ibmi.debug.status`
84
+ - `ibmi.debug.startService`
85
+ - `ibmi.debug.stopService`
86
+
87
+ ## SQL / CL
88
+ - `ibmi.sql.query`
89
+ - `ibmi.sql.execute`
90
+ - `ibmi.cl.run`
91
+
92
+ ## Diagnostics / Ops
93
+ - `ibmi.diagnostics.parseEvfevent`
94
+ - `ibmi.joblog.get`
95
+ - `ibmi.spool.list`
96
+ - `ibmi.spool.read`
97
+
98
+ ## TN5250 (Scaffold)
99
+ - `ibmi.tn5250.connect`
100
+ - `ibmi.tn5250.readScreen`
101
+ - `ibmi.tn5250.setField`
102
+ - `ibmi.tn5250.sendKeys`
103
+ - `ibmi.tn5250.waitFor`
104
+ - `ibmi.tn5250.snapshot`
105
+ - `ibmi.tn5250.disconnect`
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "mcp-for-i",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Local MCP server and control plane for IBM i workflows",
6
+ "type": "module",
7
+ "homepage": "https://github.com/ashishthomas-pcr/mcp-for-i",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ashishthomas-pcr/mcp-for-i.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ashishthomas-pcr/mcp-for-i/issues"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "docs"
19
+ ],
20
+ "bin": {
21
+ "mcp-for-i": "dist/index.js",
22
+ "mcp-for-i-control": "dist/controlplane/index.js"
23
+ },
24
+ "scripts": {
25
+ "prepack": "npm run build",
26
+ "build": "tsc -p tsconfig.json",
27
+ "start": "node dist/index.js",
28
+ "start:controlplane": "node dist/controlplane/index.js",
29
+ "dev": "ts-node src/index.ts",
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",
32
+ "test:all": "npm run build && node --test test/*.mjs",
33
+ "test:ibmi": "npm run build && node --test test/ibmi.integration.test.mjs",
34
+ "test:mcp": "npm run build && node --test test/mcp.integration.test.mjs"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.25.3",
38
+ "diff": "^5.2.0",
39
+ "keytar": "^7.9.0",
40
+ "node-ssh": "^13.1.0",
41
+ "zod-to-json-schema": "^3.23.2"
42
+ },
43
+ "devDependencies": {
44
+ "@types/diff": "^7.0.2",
45
+ "dotenv": "^17.2.3",
46
+ "ts-node": "^10.9.0",
47
+ "typescript": "^5.4.0"
48
+ }
49
+ }