mcp-for-i 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -104,6 +104,44 @@ npm test
104
104
  npm run start:controlplane
105
105
  ```
106
106
 
107
+ ## Automated Release (No Manual Version Bump)
108
+
109
+ Use the release script instead of raw `npm publish`.
110
+
111
+ Patch release:
112
+
113
+ ```bash
114
+ npm run release:patch
115
+ ```
116
+
117
+ Minor release:
118
+
119
+ ```bash
120
+ npm run release:minor
121
+ ```
122
+
123
+ Major release:
124
+
125
+ ```bash
126
+ npm run release:major
127
+ ```
128
+
129
+ What it does:
130
+ - Verifies git working tree is clean.
131
+ - Runs `npm test` (unless `--skip-tests` is passed).
132
+ - Checks npm published version.
133
+ - Auto-bumps version only when local version is not ahead.
134
+ - Publishes to npm.
135
+ - Pushes commit/tag to GitHub.
136
+
137
+ Optional flags:
138
+
139
+ ```bash
140
+ npm run release:patch -- --skip-tests
141
+ npm run release:patch -- --otp=123456
142
+ npm run release:patch -- --no-push
143
+ ```
144
+
107
145
  ## Security Model
108
146
 
109
147
  - Credential-bearing direct tool arguments are blocked for connection creation/update flows.
@@ -17,7 +17,10 @@ const defaultSettings = {
17
17
  objectFilters: [],
18
18
  ifsShortcuts: [],
19
19
  debugPort: 8005,
20
- debugSepPort: 8008
20
+ debugSepPort: 8008,
21
+ sessionIdleMinutes: 30,
22
+ sessionPingSeconds: 15,
23
+ sessionReconnectAttempts: 2
21
24
  };
22
25
  export class ConfigStore {
23
26
  configDir;
@@ -35,7 +35,7 @@ export class ConnectionService {
35
35
  port: input.port ?? existing.port,
36
36
  username: input.username ?? existing.username,
37
37
  privateKeyPath: input.privateKeyPath ?? existing.privateKeyPath,
38
- settings: input.settings ?? existing.settings,
38
+ settings: input.settings ? { ...(existing.settings || {}), ...input.settings } : existing.settings,
39
39
  policy: input.policy ?? existing.policy
40
40
  };
41
41
  await this.store.upsertConnection(next);
@@ -1,9 +1,10 @@
1
1
  import http from "http";
2
2
  import fs from "fs";
3
+ import fsp from "fs/promises";
3
4
  import path from "path";
4
5
  import { URL } from "url";
5
6
  import { fileURLToPath } from "url";
6
- import { ConfigStore } from "../config/store.js";
7
+ import { ConfigStore, getConfigDir } from "../config/store.js";
7
8
  import { isKeychainAvailable } from "../security/credentialStore.js";
8
9
  import { ConnectionService } from "./connectionService.js";
9
10
  import { RuntimeService } from "./runtimeService.js";
@@ -17,7 +18,7 @@ export async function startControlPlaneServer(opts) {
17
18
  const runtime = new RuntimeService(rootDir);
18
19
  const server = http.createServer(async (req, res) => {
19
20
  try {
20
- await routeRequest(req, res, { connections, runtime });
21
+ await routeRequest(req, res, { store, connections, runtime });
21
22
  }
22
23
  catch (err) {
23
24
  sendJson(res, 500, { error: err?.message || String(err) });
@@ -48,6 +49,42 @@ async function routeRequest(req, res, services) {
48
49
  sendJson(res, 200, { ok: true, keychainAvailable: await isKeychainAvailable() });
49
50
  return;
50
51
  }
52
+ if (method === "GET" && pathname === "/api/settings") {
53
+ const settings = await services.store.getSettings();
54
+ sendJson(res, 200, { settings });
55
+ return;
56
+ }
57
+ if (method === "PUT" && pathname === "/api/settings") {
58
+ const body = await readJson(req);
59
+ const update = {};
60
+ try {
61
+ if (typeof body.sessionIdleMinutes !== "undefined") {
62
+ update.sessionIdleMinutes = parseNumberSetting(body.sessionIdleMinutes, 1, 24 * 60, "sessionIdleMinutes");
63
+ }
64
+ if (typeof body.sessionPingSeconds !== "undefined") {
65
+ update.sessionPingSeconds = parseNumberSetting(body.sessionPingSeconds, 5, 300, "sessionPingSeconds");
66
+ }
67
+ if (typeof body.sessionReconnectAttempts !== "undefined") {
68
+ update.sessionReconnectAttempts = parseNumberSetting(body.sessionReconnectAttempts, 1, 5, "sessionReconnectAttempts");
69
+ }
70
+ }
71
+ catch (err) {
72
+ sendJson(res, 400, { error: err?.message || String(err) });
73
+ return;
74
+ }
75
+ if (Object.keys(update).length === 0) {
76
+ sendJson(res, 400, { error: "No supported settings provided" });
77
+ return;
78
+ }
79
+ await services.store.updateSettings(update);
80
+ const settings = await services.store.getSettings();
81
+ sendJson(res, 200, { settings });
82
+ return;
83
+ }
84
+ if (method === "GET" && pathname === "/api/sessions") {
85
+ sendJson(res, 200, { snapshot: await readSessionSnapshot() });
86
+ return;
87
+ }
51
88
  if (method === "GET" && pathname === "/api/runtime/status") {
52
89
  sendJson(res, 200, { jobs: services.runtime.getStatus() });
53
90
  return;
@@ -197,3 +234,27 @@ function getDefaultRootDir() {
197
234
  }
198
235
  return process.cwd();
199
236
  }
237
+ function parseNumberSetting(input, min, max, key) {
238
+ const value = Number(input);
239
+ if (!Number.isFinite(value)) {
240
+ throw new Error(`${key} must be a number`);
241
+ }
242
+ const normalized = Math.floor(value);
243
+ if (normalized < min || normalized > max) {
244
+ throw new Error(`${key} must be between ${min} and ${max}`);
245
+ }
246
+ return normalized;
247
+ }
248
+ async function readSessionSnapshot() {
249
+ const snapshotPath = path.join(getConfigDir(), "session-state.json");
250
+ try {
251
+ const raw = await fsp.readFile(snapshotPath, "utf8");
252
+ const parsed = JSON.parse(raw);
253
+ if (!parsed || typeof parsed !== "object")
254
+ return null;
255
+ return parsed;
256
+ }
257
+ catch {
258
+ return null;
259
+ }
260
+ }