mcp-for-i 0.1.4 → 0.1.6

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.
@@ -19,6 +19,55 @@ export class RuntimeService {
19
19
  getStatus() {
20
20
  return this.state;
21
21
  }
22
+ async getVersions(options) {
23
+ const mode = await this.detectExecutionModeQuiet();
24
+ const checkedAt = new Date().toISOString();
25
+ const mcpInstalled = await this.readLocalPackageVersion(this.rootDir);
26
+ const mcpLatest = await this.readLatestPublishedVersion();
27
+ const mcpStatus = mcpInstalled && mcpLatest
28
+ ? (mcpInstalled === mcpLatest ? "latest" : "update-available")
29
+ : "unknown";
30
+ const configuredRepo = sanitizeRepoUrl(options?.repoUrl) || process.env.MCP_FOR_I_SKILLS_REPO || DEFAULT_SKILLS_REPO;
31
+ const configuredBranch = sanitizeBranch(options?.branch) || process.env.MCP_FOR_I_SKILLS_BRANCH || "main";
32
+ const skillsDir = path.join(this.rootDir, "skills");
33
+ const skillsGit = path.join(skillsDir, ".git");
34
+ const skills = {
35
+ configuredRepo,
36
+ configuredBranch,
37
+ status: "not-installed"
38
+ };
39
+ if (await pathExists(skillsDir)) {
40
+ if (await pathExists(skillsGit)) {
41
+ const localRepo = await this.getGitRemoteUrlQuiet(skillsDir);
42
+ const localBranch = await this.getGitBranchQuiet(skillsDir);
43
+ const localCommit = await this.getGitCommitQuiet(skillsDir, true);
44
+ const latestCommit = await this.getRemoteBranchCommitQuiet(configuredRepo, configuredBranch, true);
45
+ skills.localRepo = localRepo;
46
+ skills.localBranch = localBranch;
47
+ skills.localCommit = localCommit;
48
+ skills.latestCommit = latestCommit;
49
+ if (localCommit && latestCommit) {
50
+ skills.status = localCommit === latestCommit ? "latest" : "update-available";
51
+ }
52
+ else {
53
+ skills.status = "unknown";
54
+ }
55
+ }
56
+ else {
57
+ skills.status = "unknown";
58
+ }
59
+ }
60
+ return {
61
+ checkedAt,
62
+ mcp: {
63
+ source: mode,
64
+ installedVersion: mcpInstalled,
65
+ latestVersion: mcpLatest,
66
+ status: mcpStatus
67
+ },
68
+ skills
69
+ };
70
+ }
22
71
  async installOrRepair() {
23
72
  return this.runJob("install", async (push) => {
24
73
  const mode = await this.detectExecutionMode(push);
@@ -239,6 +288,10 @@ export class RuntimeService {
239
288
  push("No git checkout found. Falling back to npm-based package management.");
240
289
  return "package-install";
241
290
  }
291
+ async detectExecutionModeQuiet() {
292
+ const gitDir = path.join(this.rootDir, ".git");
293
+ return await pathExists(gitDir) ? "git-checkout" : "package-install";
294
+ }
242
295
  async buildIfPresent(push) {
243
296
  const tsconfigPath = path.join(this.rootDir, "tsconfig.json");
244
297
  if (!await pathExists(tsconfigPath)) {
@@ -304,6 +357,103 @@ export class RuntimeService {
304
357
  push("Control plane will stop briefly, then restart automatically.");
305
358
  push(`Background update log: ${logPath}`);
306
359
  }
360
+ async readLocalPackageVersion(cwd) {
361
+ try {
362
+ const packagePath = path.join(cwd, "package.json");
363
+ const raw = await fs.readFile(packagePath, "utf8");
364
+ const parsed = JSON.parse(raw);
365
+ return typeof parsed.version === "string" ? parsed.version : undefined;
366
+ }
367
+ catch {
368
+ return undefined;
369
+ }
370
+ }
371
+ async readLatestPublishedVersion() {
372
+ try {
373
+ const output = await this.runNpmCapture(["view", PACKAGE_NAME, "version", "--json"], this.rootDir);
374
+ const parsed = JSON.parse(output || "null");
375
+ if (typeof parsed === "string")
376
+ return parsed.trim();
377
+ if (parsed && typeof parsed.version === "string")
378
+ return parsed.version.trim();
379
+ return undefined;
380
+ }
381
+ catch {
382
+ return undefined;
383
+ }
384
+ }
385
+ async getGitBranchQuiet(cwd) {
386
+ try {
387
+ const branch = await this.runCommandCaptureSilent("git", ["rev-parse", "--abbrev-ref", "HEAD"], cwd);
388
+ return branch || undefined;
389
+ }
390
+ catch {
391
+ return undefined;
392
+ }
393
+ }
394
+ async getGitCommitQuiet(cwd, short = false) {
395
+ try {
396
+ const args = short ? ["rev-parse", "--short", "HEAD"] : ["rev-parse", "HEAD"];
397
+ const commit = await this.runCommandCaptureSilent("git", args, cwd);
398
+ return commit || undefined;
399
+ }
400
+ catch {
401
+ return undefined;
402
+ }
403
+ }
404
+ async getGitRemoteUrlQuiet(cwd) {
405
+ try {
406
+ const value = await this.runCommandCaptureSilent("git", ["remote", "get-url", "origin"], cwd);
407
+ return value || undefined;
408
+ }
409
+ catch {
410
+ return undefined;
411
+ }
412
+ }
413
+ async getRemoteBranchCommitQuiet(repoUrl, branch, short = false) {
414
+ try {
415
+ const out = await this.runCommandCaptureSilent("git", ["ls-remote", repoUrl, branch], this.rootDir);
416
+ const firstLine = out.split(/\r?\n/).find(Boolean);
417
+ if (!firstLine)
418
+ return undefined;
419
+ const [hash] = firstLine.trim().split(/\s+/);
420
+ if (!hash)
421
+ return undefined;
422
+ return short ? hash.slice(0, 7) : hash;
423
+ }
424
+ catch {
425
+ return undefined;
426
+ }
427
+ }
428
+ async runNpmCapture(args, cwd) {
429
+ const npm = await resolveNpmLaunch();
430
+ if (npm.mode === "node-cli") {
431
+ return this.runCommandCaptureSilent(process.execPath, [npm.path, ...args], cwd);
432
+ }
433
+ return this.runCommandCaptureSilent(npm.path, args, cwd);
434
+ }
435
+ runCommandCaptureSilent(command, args, cwd) {
436
+ return new Promise((resolve, reject) => {
437
+ const child = spawn(command, args, {
438
+ cwd,
439
+ shell: process.platform === "win32"
440
+ });
441
+ const stdout = [];
442
+ const stderr = [];
443
+ child.stdout.on("data", chunk => stdout.push(String(chunk)));
444
+ child.stderr.on("data", chunk => stderr.push(String(chunk)));
445
+ child.on("error", reject);
446
+ child.on("exit", code => {
447
+ if (code === 0) {
448
+ resolve(stdout.join("").trim());
449
+ }
450
+ else {
451
+ const errText = stderr.join("").trim();
452
+ reject(new Error(errText || `Command failed (${code}): ${command} ${args.join(" ")}`));
453
+ }
454
+ });
455
+ });
456
+ }
307
457
  }
308
458
  function makeJob(id) {
309
459
  return {
@@ -52,6 +52,14 @@ async function routeRequest(req, res, services) {
52
52
  sendJson(res, 200, { jobs: services.runtime.getStatus() });
53
53
  return;
54
54
  }
55
+ if (method === "GET" && pathname === "/api/runtime/versions") {
56
+ const versions = await services.runtime.getVersions({
57
+ repoUrl: url.searchParams.get("repoUrl") || undefined,
58
+ branch: url.searchParams.get("branch") || undefined
59
+ });
60
+ sendJson(res, 200, versions);
61
+ return;
62
+ }
55
63
  if (method === "POST" && pathname === "/api/runtime/install") {
56
64
  const job = await services.runtime.installOrRepair();
57
65
  sendJson(res, 202, { job });