frontmcp 1.5.5 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -29,9 +29,9 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^0.10.0",
32
- "@frontmcp/lazy-zod": "1.5.5",
33
- "@frontmcp/skills": "1.5.5",
34
- "@frontmcp/utils": "1.5.5",
32
+ "@frontmcp/lazy-zod": "1.5.7",
33
+ "@frontmcp/skills": "1.5.7",
34
+ "@frontmcp/utils": "1.5.7",
35
35
  "@rspack/core": "^1.7.6",
36
36
  "commander": "^13.0.0",
37
37
  "esbuild": "^0.27.3",
@@ -10,6 +10,7 @@ const os = tslib_1.__importStar(require("os"));
10
10
  const path = tslib_1.__importStar(require("path"));
11
11
  const utils_1 = require("@frontmcp/utils");
12
12
  const colors_1 = require("../../core/colors");
13
+ const plugin_emitter_1 = require("../build/exec/cli-runtime/plugin-emitter");
13
14
  const paths_1 = require("../pm/paths");
14
15
  const questionnaire_1 = require("./questionnaire");
15
16
  const registry_1 = require("./registry");
@@ -63,15 +64,19 @@ async function runInstall(opts) {
63
64
  'frontmcp.config.js (or frontmcp.config.json) or was built with "frontmcp build --target node".');
64
65
  }
65
66
  const { data: manifestData, dir: manifestDir } = manifest;
67
+ (0, plugin_emitter_1.assertValidPluginName)(manifestData.name, 'frontmcp install');
66
68
  // 4. Install to ~/.frontmcp/apps/{name}/
67
69
  const installDir = (0, paths_1.appDir)(manifestData.name);
70
+ if (!(0, plugin_emitter_1.isPluginContainedPath)(installDir, manifestData.bundle)) {
71
+ throw new Error(`Invalid manifest bundle "${String(manifestData.bundle)}": must be a relative path inside the app directory.`);
72
+ }
68
73
  (0, paths_1.ensurePmDirs)();
69
74
  fs.mkdirSync(installDir, { recursive: true });
70
75
  console.log(`${(0, colors_1.c)('cyan', '[install]')} installing "${manifestData.name}" to ${installDir}`);
71
76
  // Copy bundle + manifest + runner
72
- copyIfExists(manifestDir, installDir, manifestData.bundle);
73
- copyIfExists(manifestDir, installDir, `${manifestData.name}.manifest.json`);
74
- copyIfExists(manifestDir, installDir, manifestData.name);
77
+ await copyIfExists(manifestDir, installDir, manifestData.bundle);
78
+ await copyIfExists(manifestDir, installDir, `${manifestData.name}.manifest.json`);
79
+ await copyIfExists(manifestDir, installDir, manifestData.name);
75
80
  // Make runner executable
76
81
  const runnerPath = path.join(installDir, manifestData.name);
77
82
  if (fs.existsSync(runnerPath)) {
@@ -146,10 +151,32 @@ function findManifest(dir) {
146
151
  }
147
152
  return null;
148
153
  }
149
- function copyIfExists(fromDir, toDir, filename) {
150
- const src = path.join(fromDir, filename);
151
- if (fs.existsSync(src)) {
152
- fs.copyFileSync(src, path.join(toDir, filename));
154
+ /**
155
+ * Resolve `filename` inside `baseDir`, returning the real path only when it is a regular file
156
+ * that stays within the directory. Both operands are resolved, so a symlink in the fetched
157
+ * package cannot make the copy read a file elsewhere on the host, and a device or fifo cannot
158
+ * make it read forever.
159
+ */
160
+ async function resolveContainedFile(baseDir, filename) {
161
+ try {
162
+ const base = await (0, utils_1.realpath)(baseDir);
163
+ const resolved = await (0, utils_1.realpath)(path.join(base, filename));
164
+ if (!(0, plugin_emitter_1.isPluginContainedPath)(base, path.relative(base, resolved)))
165
+ return null;
166
+ return (await (0, utils_1.stat)(resolved)).isFile() ? resolved : null;
153
167
  }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ async function copyIfExists(fromDir, toDir, filename) {
173
+ if (!(0, plugin_emitter_1.isPluginContainedPath)(toDir, filename))
174
+ return;
175
+ const src = await resolveContainedFile(fromDir, filename);
176
+ if (!src)
177
+ return;
178
+ const dest = path.join(toDir, filename);
179
+ await (0, utils_1.ensureDir)(path.dirname(dest));
180
+ await (0, utils_1.copyFile)(src, dest);
154
181
  }
155
182
  //# sourceMappingURL=install.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/commands/package/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * runInstall() orchestrator — installs an MCP app from npm, local, or git source.\n */\n\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { runCmd } from '@frontmcp/utils';\n\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { type ExecManifest } from '../build/exec/manifest';\nimport { appDir, ensurePmDirs } from '../pm/paths';\nimport { runQuestionnaire, writeEnvFile } from './questionnaire';\nimport { registerApp } from './registry';\nimport { fetchFromGit } from './sources/git';\nimport { fetchFromLocal } from './sources/local';\nimport { fetchFromNpm } from './sources/npm';\nimport { parseInstallSource } from './types';\n\nexport async function runInstall(opts: ParsedArgs): Promise<void> {\n const sourceStr = opts._[1];\n if (!sourceStr) {\n throw new Error('Missing install source. Usage: frontmcp install <npm-package|./local-path|github:user/repo>');\n }\n\n const source = parseInstallSource(sourceStr);\n console.log(`${c('cyan', '[install]')} source: ${source.type} → ${source.ref}`);\n\n // Create temp directory\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frontmcp-install-'));\n\n try {\n // 1. Fetch to temp directory\n console.log(`${c('cyan', '[install]')} fetching...`);\n let packageDir: string;\n\n switch (source.type) {\n case 'npm':\n packageDir = await fetchFromNpm(source.ref, tmpDir, opts.registry);\n break;\n case 'local':\n packageDir = await fetchFromLocal(source.ref, tmpDir);\n break;\n case 'git':\n packageDir = await fetchFromGit(source.ref, tmpDir);\n break;\n case 'esm':\n throw new Error(\n 'ESM sources cannot be installed via \"frontmcp install\". ' +\n 'Use loadFrom() in your FrontMcp config to load ESM packages at runtime.',\n );\n }\n\n // 2. Look for manifest\n let manifest = findManifest(packageDir);\n\n // 3. If no manifest, check for frontmcp.config.js and build\n if (!manifest) {\n const configPath = path.join(packageDir, 'frontmcp.config.js');\n const configJsonPath = path.join(packageDir, 'frontmcp.config.json');\n\n if (fs.existsSync(configPath) || fs.existsSync(configJsonPath)) {\n console.log(`${c('cyan', '[install]')} no manifest found, building from config...`);\n await runCmd('npx', ['frontmcp', 'build', '--target', 'node'], {\n cwd: packageDir,\n });\n manifest = findManifest(path.join(packageDir, 'dist')) || findManifest(packageDir);\n }\n }\n\n if (!manifest) {\n throw new Error(\n 'Could not find or generate a manifest. Ensure the package has a ' +\n 'frontmcp.config.js (or frontmcp.config.json) or was built with \"frontmcp build --target node\".',\n );\n }\n\n const { data: manifestData, dir: manifestDir } = manifest;\n\n // 4. Install to ~/.frontmcp/apps/{name}/\n const installDir = appDir(manifestData.name);\n ensurePmDirs();\n fs.mkdirSync(installDir, { recursive: true });\n\n console.log(`${c('cyan', '[install]')} installing \"${manifestData.name}\" to ${installDir}`);\n\n // Copy bundle + manifest + runner\n copyIfExists(manifestDir, installDir, manifestData.bundle);\n copyIfExists(manifestDir, installDir, `${manifestData.name}.manifest.json`);\n copyIfExists(manifestDir, installDir, manifestData.name);\n\n // Make runner executable\n const runnerPath = path.join(installDir, manifestData.name);\n if (fs.existsSync(runnerPath)) {\n fs.chmodSync(runnerPath, 0o755);\n }\n\n // 5. Install native addons\n if (manifestData.dependencies.nativeAddons.length > 0) {\n console.log(`${c('cyan', '[install]')} installing native dependencies...`);\n await runCmd('npm', ['init', '-y', '--silent'], { cwd: installDir });\n await runCmd('npm', ['install', ...manifestData.dependencies.nativeAddons, '--save', '--silent'], {\n cwd: installDir,\n });\n }\n\n // 6. Set up SQLite data dir if needed\n if (manifestData.storage.type === 'sqlite') {\n const dataDir = path.join(os.homedir(), '.frontmcp', 'data', manifestData.name);\n fs.mkdirSync(dataDir, { recursive: true });\n }\n\n // 7. Run setup questionnaire\n if (manifestData.setup?.steps && manifestData.setup.steps.length > 0) {\n console.log(`\\n${c('bold', 'Setup Configuration')}`);\n const result = await runQuestionnaire(manifestData.setup.steps, {\n silent: opts.yes,\n });\n writeEnvFile(installDir, result.envContent);\n console.log(`${c('green', '[install]')} configuration saved to .env`);\n }\n\n // 8. Register in registry. CLI builds omit the network section (they don't\n // bind a port), so fall back to undefined when --port wasn't passed.\n // Use `??` (not `||`) so an explicit `--port 0` overrides the manifest.\n const port = opts.port ?? manifestData.network?.defaultPort;\n registerApp(manifestData.name, {\n version: manifestData.version,\n installDir,\n installedAt: new Date().toISOString(),\n runner: path.join(installDir, manifestData.name),\n bundle: path.join(installDir, manifestData.bundle),\n storage: manifestData.storage.type,\n port,\n source: { type: source.type, ref: source.ref },\n });\n\n console.log(`\\n${c('green', `Installed \"${manifestData.name}\" successfully.`)}`);\n console.log(`\\n${c('bold', 'Start with:')}`);\n console.log(` frontmcp start ${manifestData.name}`);\n console.log(`\\n${c('bold', 'Reconfigure:')}`);\n console.log(` frontmcp configure ${manifestData.name}`);\n } finally {\n // Clean up temp directory\n try {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // ignore cleanup errors\n }\n }\n}\n\nfunction findManifest(dir: string): { data: ExecManifest; dir: string } | null {\n if (!fs.existsSync(dir)) return null;\n\n const files = fs.readdirSync(dir);\n const manifestFile = files.find((f: string) => f.endsWith('.manifest.json'));\n\n if (manifestFile) {\n const manifestPath = path.join(dir, manifestFile);\n const data = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as ExecManifest;\n return { data, dir };\n }\n\n // Check dist/ subdirectory\n const distDir = path.join(dir, 'dist');\n if (fs.existsSync(distDir)) {\n return findManifest(distDir);\n }\n\n return null;\n}\n\nfunction copyIfExists(fromDir: string, toDir: string, filename: string): void {\n const src = path.join(fromDir, filename);\n if (fs.existsSync(src)) {\n fs.copyFileSync(src, path.join(toDir, filename));\n }\n}\n"]}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/commands/package/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * runInstall() orchestrator — installs an MCP app from npm, local, or git source.\n */\n\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { copyFile, ensureDir, realpath, runCmd, stat } from '@frontmcp/utils';\n\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { assertValidPluginName, isPluginContainedPath } from '../build/exec/cli-runtime/plugin-emitter';\nimport { type ExecManifest } from '../build/exec/manifest';\nimport { appDir, ensurePmDirs } from '../pm/paths';\nimport { runQuestionnaire, writeEnvFile } from './questionnaire';\nimport { registerApp } from './registry';\nimport { fetchFromGit } from './sources/git';\nimport { fetchFromLocal } from './sources/local';\nimport { fetchFromNpm } from './sources/npm';\nimport { parseInstallSource } from './types';\n\nexport async function runInstall(opts: ParsedArgs): Promise<void> {\n const sourceStr = opts._[1];\n if (!sourceStr) {\n throw new Error('Missing install source. Usage: frontmcp install <npm-package|./local-path|github:user/repo>');\n }\n\n const source = parseInstallSource(sourceStr);\n console.log(`${c('cyan', '[install]')} source: ${source.type} → ${source.ref}`);\n\n // Create temp directory\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frontmcp-install-'));\n\n try {\n // 1. Fetch to temp directory\n console.log(`${c('cyan', '[install]')} fetching...`);\n let packageDir: string;\n\n switch (source.type) {\n case 'npm':\n packageDir = await fetchFromNpm(source.ref, tmpDir, opts.registry);\n break;\n case 'local':\n packageDir = await fetchFromLocal(source.ref, tmpDir);\n break;\n case 'git':\n packageDir = await fetchFromGit(source.ref, tmpDir);\n break;\n case 'esm':\n throw new Error(\n 'ESM sources cannot be installed via \"frontmcp install\". ' +\n 'Use loadFrom() in your FrontMcp config to load ESM packages at runtime.',\n );\n }\n\n // 2. Look for manifest\n let manifest = findManifest(packageDir);\n\n // 3. If no manifest, check for frontmcp.config.js and build\n if (!manifest) {\n const configPath = path.join(packageDir, 'frontmcp.config.js');\n const configJsonPath = path.join(packageDir, 'frontmcp.config.json');\n\n if (fs.existsSync(configPath) || fs.existsSync(configJsonPath)) {\n console.log(`${c('cyan', '[install]')} no manifest found, building from config...`);\n await runCmd('npx', ['frontmcp', 'build', '--target', 'node'], {\n cwd: packageDir,\n });\n manifest = findManifest(path.join(packageDir, 'dist')) || findManifest(packageDir);\n }\n }\n\n if (!manifest) {\n throw new Error(\n 'Could not find or generate a manifest. Ensure the package has a ' +\n 'frontmcp.config.js (or frontmcp.config.json) or was built with \"frontmcp build --target node\".',\n );\n }\n\n const { data: manifestData, dir: manifestDir } = manifest;\n\n assertValidPluginName(manifestData.name, 'frontmcp install');\n\n // 4. Install to ~/.frontmcp/apps/{name}/\n const installDir = appDir(manifestData.name);\n\n if (!isPluginContainedPath(installDir, manifestData.bundle)) {\n throw new Error(\n `Invalid manifest bundle \"${String(manifestData.bundle)}\": must be a relative path inside the app directory.`,\n );\n }\n\n ensurePmDirs();\n fs.mkdirSync(installDir, { recursive: true });\n\n console.log(`${c('cyan', '[install]')} installing \"${manifestData.name}\" to ${installDir}`);\n\n // Copy bundle + manifest + runner\n await copyIfExists(manifestDir, installDir, manifestData.bundle);\n await copyIfExists(manifestDir, installDir, `${manifestData.name}.manifest.json`);\n await copyIfExists(manifestDir, installDir, manifestData.name);\n\n // Make runner executable\n const runnerPath = path.join(installDir, manifestData.name);\n if (fs.existsSync(runnerPath)) {\n fs.chmodSync(runnerPath, 0o755);\n }\n\n // 5. Install native addons\n if (manifestData.dependencies.nativeAddons.length > 0) {\n console.log(`${c('cyan', '[install]')} installing native dependencies...`);\n await runCmd('npm', ['init', '-y', '--silent'], { cwd: installDir });\n await runCmd('npm', ['install', ...manifestData.dependencies.nativeAddons, '--save', '--silent'], {\n cwd: installDir,\n });\n }\n\n // 6. Set up SQLite data dir if needed\n if (manifestData.storage.type === 'sqlite') {\n const dataDir = path.join(os.homedir(), '.frontmcp', 'data', manifestData.name);\n fs.mkdirSync(dataDir, { recursive: true });\n }\n\n // 7. Run setup questionnaire\n if (manifestData.setup?.steps && manifestData.setup.steps.length > 0) {\n console.log(`\\n${c('bold', 'Setup Configuration')}`);\n const result = await runQuestionnaire(manifestData.setup.steps, {\n silent: opts.yes,\n });\n writeEnvFile(installDir, result.envContent);\n console.log(`${c('green', '[install]')} configuration saved to .env`);\n }\n\n // 8. Register in registry. CLI builds omit the network section (they don't\n // bind a port), so fall back to undefined when --port wasn't passed.\n // Use `??` (not `||`) so an explicit `--port 0` overrides the manifest.\n const port = opts.port ?? manifestData.network?.defaultPort;\n registerApp(manifestData.name, {\n version: manifestData.version,\n installDir,\n installedAt: new Date().toISOString(),\n runner: path.join(installDir, manifestData.name),\n bundle: path.join(installDir, manifestData.bundle),\n storage: manifestData.storage.type,\n port,\n source: { type: source.type, ref: source.ref },\n });\n\n console.log(`\\n${c('green', `Installed \"${manifestData.name}\" successfully.`)}`);\n console.log(`\\n${c('bold', 'Start with:')}`);\n console.log(` frontmcp start ${manifestData.name}`);\n console.log(`\\n${c('bold', 'Reconfigure:')}`);\n console.log(` frontmcp configure ${manifestData.name}`);\n } finally {\n // Clean up temp directory\n try {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // ignore cleanup errors\n }\n }\n}\n\nfunction findManifest(dir: string): { data: ExecManifest; dir: string } | null {\n if (!fs.existsSync(dir)) return null;\n\n const files = fs.readdirSync(dir);\n const manifestFile = files.find((f: string) => f.endsWith('.manifest.json'));\n\n if (manifestFile) {\n const manifestPath = path.join(dir, manifestFile);\n const data = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as ExecManifest;\n return { data, dir };\n }\n\n // Check dist/ subdirectory\n const distDir = path.join(dir, 'dist');\n if (fs.existsSync(distDir)) {\n return findManifest(distDir);\n }\n\n return null;\n}\n\n/**\n * Resolve `filename` inside `baseDir`, returning the real path only when it is a regular file\n * that stays within the directory. Both operands are resolved, so a symlink in the fetched\n * package cannot make the copy read a file elsewhere on the host, and a device or fifo cannot\n * make it read forever.\n */\nasync function resolveContainedFile(baseDir: string, filename: string): Promise<string | null> {\n try {\n const base = await realpath(baseDir);\n const resolved = await realpath(path.join(base, filename));\n\n if (!isPluginContainedPath(base, path.relative(base, resolved))) return null;\n\n return (await stat(resolved)).isFile() ? resolved : null;\n } catch {\n return null;\n }\n}\n\nasync function copyIfExists(fromDir: string, toDir: string, filename: string): Promise<void> {\n if (!isPluginContainedPath(toDir, filename)) return;\n\n const src = await resolveContainedFile(fromDir, filename);\n if (!src) return;\n\n const dest = path.join(toDir, filename);\n await ensureDir(path.dirname(dest));\n await copyFile(src, dest);\n}\n"]}