frontmcp 0.3.0 → 0.4.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 (45) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +448 -0
  3. package/package.json +4 -4
  4. package/src/args.d.ts +8 -0
  5. package/src/args.js +19 -0
  6. package/src/args.js.map +1 -0
  7. package/src/cli.d.ts +0 -1
  8. package/src/cli.js +30 -574
  9. package/src/cli.js.map +1 -1
  10. package/src/colors.d.ts +12 -0
  11. package/src/colors.js +17 -0
  12. package/src/colors.js.map +1 -0
  13. package/src/commands/build.d.ts +2 -0
  14. package/src/commands/build.js +43 -0
  15. package/src/commands/build.js.map +1 -0
  16. package/src/commands/create.d.ts +1 -0
  17. package/src/commands/create.js +204 -0
  18. package/src/commands/create.js.map +1 -0
  19. package/src/commands/dev.d.ts +2 -0
  20. package/src/commands/dev.js +49 -0
  21. package/src/commands/dev.js.map +1 -0
  22. package/src/commands/doctor.d.ts +1 -0
  23. package/src/commands/doctor.js +87 -0
  24. package/src/commands/doctor.js.map +1 -0
  25. package/src/commands/inspector.d.ts +1 -0
  26. package/src/commands/inspector.js +10 -0
  27. package/src/commands/inspector.js.map +1 -0
  28. package/src/commands/template.d.ts +13 -0
  29. package/src/commands/template.js +179 -0
  30. package/src/commands/template.js.map +1 -0
  31. package/src/templates/3rd-party-integration/src/http-client.d.ts +20 -0
  32. package/src/templates/3rd-party-integration/src/http-client.js +101 -0
  33. package/src/templates/3rd-party-integration/src/http-client.js.map +1 -0
  34. package/src/templates/3rd-party-integration/src/mcp-http-types.d.ts +199 -0
  35. package/src/templates/3rd-party-integration/src/mcp-http-types.js +51 -0
  36. package/src/templates/3rd-party-integration/src/mcp-http-types.js.map +1 -0
  37. package/src/templates/3rd-party-integration/src/tools/example.list.d.ts +125 -0
  38. package/src/templates/3rd-party-integration/src/tools/example.list.js +85 -0
  39. package/src/templates/3rd-party-integration/src/tools/example.list.js.map +1 -0
  40. package/src/tsconfig.d.ts +34 -0
  41. package/src/tsconfig.js +103 -0
  42. package/src/tsconfig.js.map +1 -0
  43. package/src/utils/fs.d.ts +11 -0
  44. package/src/utils/fs.js +104 -0
  45. package/src/utils/fs.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":";;AASA,8BAUC;AAVD,SAAgB,SAAS,CAAC,IAAc;IACtC,MAAM,GAAG,GAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;aACvD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;aACzD,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;;YAClD,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["export type Command = 'dev' | 'build' | 'init' | 'doctor' | 'inspector' | 'create' | 'help' | 'template' | 'version';\n\nexport interface ParsedArgs {\n _: string[];\n outDir?: string;\n entry?: string;\n help?: boolean;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = { _: [] };\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === '--out-dir' || a === '-o') out.outDir = argv[++i];\n else if (a === '--entry' || a === '-e') out.entry = argv[++i];\n else if (a === '--help' || a === '-h') out.help = true;\n else out._.push(a);\n }\n return out;\n}\n"]}
package/src/cli.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * frontmcp - FrontMCP command line interface
4
- * Save as bin/frontmcp.ts (compile to JS with shebang preserved) or run with tsx.
5
4
  */
6
5
  export {};
package/src/cli.js CHANGED
@@ -2,600 +2,51 @@
2
2
  "use strict";
3
3
  /**
4
4
  * frontmcp - FrontMCP command line interface
5
- * Save as bin/frontmcp.ts (compile to JS with shebang preserved) or run with tsx.
6
5
  */
7
6
  Object.defineProperty(exports, "__esModule", { value: true });
8
- const tslib_1 = require("tslib");
9
- const fs = tslib_1.__importStar(require("fs"));
10
- const fs_1 = require("fs");
11
- const path = tslib_1.__importStar(require("path"));
12
- const child_process_1 = require("child_process");
13
- const version_1 = require("./version");
14
- const COLORS = {
15
- reset: '\x1b[0m',
16
- bold: '\x1b[1m',
17
- dim: '\x1b[2m',
18
- red: '\x1b[31m',
19
- green: '\x1b[32m',
20
- yellow: '\x1b[33m',
21
- blue: '\x1b[34m',
22
- cyan: '\x1b[36m',
23
- gray: '\x1b[90m',
24
- };
25
- const c = (color, s) => COLORS[color] + s + COLORS.reset;
7
+ const colors_1 = require("./colors");
8
+ const args_1 = require("./args");
9
+ const dev_1 = require("./commands/dev");
10
+ const build_1 = require("./commands/build");
11
+ const tsconfig_1 = require("./tsconfig");
12
+ const doctor_1 = require("./commands/doctor");
13
+ const inspector_1 = require("./commands/inspector");
14
+ const create_1 = require("./commands/create");
15
+ const template_1 = require("./commands/template");
26
16
  function showHelp() {
27
17
  console.log(`
28
- ${c('bold', 'frontmcp')} — FrontMCP command line interface
18
+ ${(0, colors_1.c)('bold', 'frontmcp')} — FrontMCP command line interface
29
19
 
30
- ${c('bold', 'Usage')}
20
+ ${(0, colors_1.c)('bold', 'Usage')}
31
21
  frontmcp <command> [options]
32
22
 
33
- ${c('bold', 'Commands')}
23
+ ${(0, colors_1.c)('bold', 'Commands')}
34
24
  dev Start in development mode (tsx --watch <entry> + async type-check)
35
25
  build Compile entry with TypeScript (tsc)
36
26
  init Create or fix a tsconfig.json suitable for FrontMCP
37
27
  doctor Check Node/npm versions and tsconfig requirements
38
28
  inspector Launch MCP Inspector (npx @modelcontextprotocol/inspector)
39
29
  create <name> Scaffold a new FrontMCP project in ./<name>
30
+ template <type> Scaffold a template by type (e.g., "3rd-party-integration")
40
31
  help Show this help message
41
32
 
42
- ${c('bold', 'Options')}
33
+ ${(0, colors_1.c)('bold', 'Options')}
43
34
  -o, --out-dir <dir> Output directory (default: ./dist)
44
35
  -e, --entry <path> Manually specify entry file path
45
36
 
46
- ${c('bold', 'Examples')}
37
+ ${(0, colors_1.c)('bold', 'Examples')}
47
38
  frontmcp dev
48
39
  frontmcp build --out-dir build
49
40
  frontmcp init
50
41
  frontmcp doctor
51
42
  frontmcp inspector
52
43
  npx frontmcp create my-mcp
44
+ npx frontmcp template marketplace-3rd-tools
53
45
  `);
54
46
  }
55
- function parseArgs(argv) {
56
- const out = { _: [] };
57
- for (let i = 0; i < argv.length; i++) {
58
- const a = argv[i];
59
- if (a === '--out-dir' || a === '-o')
60
- out.outDir = argv[++i];
61
- else if (a === '--entry' || a === '-e')
62
- out.entry = argv[++i];
63
- else if (a === '--help' || a === '-h')
64
- out.help = true;
65
- else
66
- out._.push(a);
67
- }
68
- return out;
69
- }
70
- async function fileExists(p) {
71
- try {
72
- await fs_1.promises.access(p, fs.constants.F_OK);
73
- return true;
74
- }
75
- catch {
76
- return false;
77
- }
78
- }
79
- async function readJSON(jsonPath) {
80
- try {
81
- const buf = await fs_1.promises.readFile(jsonPath, 'utf8');
82
- return JSON.parse(buf);
83
- }
84
- catch {
85
- return null;
86
- }
87
- }
88
- async function writeJSON(p, obj) {
89
- await fs_1.promises.writeFile(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
90
- }
91
- function tryCandidates(base) {
92
- const exts = ['', '.ts', '.tsx', '.js', '.mjs', '.cjs'];
93
- return exts.map((ext) => base + ext);
94
- }
95
- async function resolveEntry(cwd, explicit) {
96
- if (explicit) {
97
- const full = path.resolve(cwd, explicit);
98
- if (await fileExists(full))
99
- return full;
100
- throw new Error(`Entry override not found: ${explicit}`);
101
- }
102
- const pkgPath = path.join(cwd, 'package.json');
103
- if (await fileExists(pkgPath)) {
104
- const pkg = await readJSON(pkgPath);
105
- if (pkg && typeof pkg.main === 'string' && pkg.main.trim()) {
106
- const mainCandidates = tryCandidates(path.resolve(cwd, pkg.main));
107
- for (const p of mainCandidates) {
108
- if (await fileExists(p))
109
- return p;
110
- }
111
- const asDir = path.resolve(cwd, pkg.main);
112
- const idxCandidates = tryCandidates(path.join(asDir, 'index'));
113
- for (const p of idxCandidates) {
114
- if (await fileExists(p))
115
- return p;
116
- }
117
- }
118
- }
119
- const fallback = path.join(cwd, 'src', 'main.ts');
120
- if (await fileExists(fallback))
121
- return fallback;
122
- const msg = [
123
- c('red', 'No entry file found.'),
124
- '',
125
- 'I looked for:',
126
- ` • ${pkgPath} with a valid "main" field`,
127
- ` • ${path.relative(cwd, fallback)}`,
128
- '',
129
- 'Please create an entry file (e.g. src/main.ts) or set "main" in package.json,',
130
- 'or run with an explicit path:',
131
- ` frontmcp dev --entry src/main.ts`,
132
- ].join('\n');
133
- throw new Error(msg);
134
- }
135
- function runCmd(cmd, args, opts = {}) {
136
- return new Promise((resolve, reject) => {
137
- const child = (0, child_process_1.spawn)(cmd, args, { stdio: 'inherit', shell: true, ...opts });
138
- child.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`${cmd} exited with code ${code}`))));
139
- child.on('error', reject);
140
- });
141
- }
142
- async function ensureDir(p) {
143
- await fs_1.promises.mkdir(p, { recursive: true });
144
- }
145
- async function isDirEmpty(dir) {
146
- try {
147
- const items = await fs_1.promises.readdir(dir);
148
- return items.length === 0;
149
- }
150
- catch (e) {
151
- if (e?.code === 'ENOENT')
152
- return true;
153
- throw e;
154
- }
155
- }
156
- function sanitizeForFolder(name) {
157
- const seg = name.startsWith('@') && name.includes('/') ? name.split('/')[1] : name;
158
- return (seg
159
- .replace(/[^a-zA-Z0-9._-]/g, '-')
160
- .replace(/-+/g, '-')
161
- .replace(/^-|-$/g, '')
162
- .toLowerCase() || 'frontmcp-app');
163
- }
164
- function sanitizeForNpm(name) {
165
- if (name.startsWith('@') && name.includes('/')) {
166
- const [scope, pkg] = name.split('/');
167
- const s = scope.replace(/[^a-z0-9-]/gi, '').toLowerCase();
168
- const p = pkg.replace(/[^a-z0-9._-]/gi, '-').toLowerCase();
169
- return `@${s}/${p || 'frontmcp-app'}`;
170
- }
171
- return name.replace(/[^a-z0-9._-]/gi, '-').toLowerCase() || 'frontmcp-app';
172
- }
173
- /* --------------------------------- Actions -------------------------------- */
174
- function isTsLike(p) {
175
- return /\.tsx?$/i.test(p);
176
- }
177
- function killQuiet(proc) {
178
- try {
179
- if (proc) {
180
- proc.kill('SIGINT');
181
- }
182
- }
183
- catch {
184
- // Intentionally ignore shutdown errors.
185
- }
186
- }
187
- async function runDev(opts) {
188
- const cwd = process.cwd();
189
- const entry = await resolveEntry(cwd, opts.entry);
190
- console.log(`${c('cyan', '[dev]')} using entry: ${path.relative(cwd, entry)}`);
191
- console.log(`${c('gray', '[dev]')} starting ${c('bold', 'tsx --watch')} and ${c('bold', 'tsc --noEmit --watch')} (async type-checker)`);
192
- console.log(`${c('gray', 'hint:')} press Ctrl+C to stop`);
193
- // Start tsx watcher (app run)
194
- const app = (0, child_process_1.spawn)('npx', ['-y', 'tsx', '--watch', entry], { stdio: 'inherit', shell: true });
195
- // Start tsc in watch mode for async type-checking (non-blocking)
196
- const checker = (0, child_process_1.spawn)('npx', ['-y', 'tsc', '--noEmit', '--pretty', '--watch'], {
197
- stdio: 'inherit',
198
- shell: true,
199
- });
200
- const cleanup = () => {
201
- killQuiet(checker);
202
- killQuiet(app);
203
- };
204
- process.on('SIGINT', () => {
205
- cleanup();
206
- process.exit(0);
207
- });
208
- await new Promise((resolve, reject) => {
209
- app.on('close', (_code) => {
210
- // When app exits, stop checker too.
211
- cleanup();
212
- resolve();
213
- });
214
- app.on('error', (err) => {
215
- cleanup();
216
- reject(err);
217
- });
218
- });
219
- }
220
- async function runBuild(opts) {
221
- const cwd = process.cwd();
222
- const entry = await resolveEntry(cwd, opts.entry);
223
- const outDir = path.resolve(cwd, opts.outDir || 'dist');
224
- await ensureDir(outDir);
225
- console.log(`${c('cyan', '[build]')} entry: ${path.relative(cwd, entry)}`);
226
- console.log(`${c('cyan', '[build]')} outDir: ${path.relative(cwd, outDir)}`);
227
- const tsconfigPath = path.join(cwd, 'tsconfig.json');
228
- const hasTsconfig = await fileExists(tsconfigPath);
229
- const args = ['-y', 'tsc'];
230
- if (hasTsconfig) {
231
- console.log(c('gray', `[build] tsconfig.json detected — compiling with project settings`));
232
- args.push('--project', tsconfigPath);
233
- }
234
- else {
235
- args.push(entry);
236
- args.push('--rootDir', path.dirname(entry));
237
- if (!isTsLike(entry)) {
238
- args.push('--allowJs');
239
- console.log(c('yellow', '[build] Entry is not TypeScript; enabling --allowJs'));
240
- }
241
- args.push('--experimentalDecorators', '--emitDecoratorMetadata');
242
- args.push('--module', REQUIRED_DECORATOR_FIELDS.module);
243
- args.push('--target', REQUIRED_DECORATOR_FIELDS.target);
244
- }
245
- args.push('--outDir', outDir);
246
- args.push('--skipLibCheck');
247
- await runCmd('npx', args);
248
- console.log(c('green', '✅ Build completed.'));
249
- console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));
250
- }
251
- /* --------------------------- tsconfig management --------------------------- */
252
- const REQUIRED_DECORATOR_FIELDS = {
253
- target: 'es2021',
254
- module: 'esnext',
255
- emitDecoratorMetadata: true,
256
- experimentalDecorators: true,
257
- strictFunctionTypes: true,
258
- moduleResolution: 'node',
259
- };
260
- const RECOMMENDED_TSCONFIG = {
261
- compilerOptions: {
262
- target: REQUIRED_DECORATOR_FIELDS.target,
263
- module: REQUIRED_DECORATOR_FIELDS.module,
264
- emitDecoratorMetadata: REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata,
265
- experimentalDecorators: REQUIRED_DECORATOR_FIELDS.experimentalDecorators,
266
- strictFunctionTypes: REQUIRED_DECORATOR_FIELDS.strictFunctionTypes,
267
- moduleResolution: REQUIRED_DECORATOR_FIELDS.moduleResolution,
268
- strict: true,
269
- esModuleInterop: true,
270
- resolveJsonModule: true,
271
- skipLibCheck: true,
272
- sourceMap: true,
273
- outDir: 'dist',
274
- rootDir: 'src',
275
- types: ['node'],
276
- },
277
- include: ['src/**/*'],
278
- };
279
- function deepMerge(base, patch) {
280
- const out = { ...base };
281
- for (const [k, v] of Object.entries(patch)) {
282
- if (v && typeof v === 'object' && !Array.isArray(v)) {
283
- out[k] = deepMerge(base[k] ?? {}, v);
284
- }
285
- else {
286
- out[k] = v;
287
- }
288
- }
289
- return out;
290
- }
291
- function ensureRequiredTsOptions(obj) {
292
- const next = { ...obj };
293
- next.compilerOptions = { ...(next.compilerOptions || {}) };
294
- next.compilerOptions.target = REQUIRED_DECORATOR_FIELDS.target;
295
- next.compilerOptions.module = REQUIRED_DECORATOR_FIELDS.module;
296
- next.compilerOptions.emitDecoratorMetadata = REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata;
297
- next.compilerOptions.experimentalDecorators = REQUIRED_DECORATOR_FIELDS.experimentalDecorators;
298
- return next;
299
- }
300
- function normalizeStr(x) {
301
- return typeof x === 'string' ? x.toLowerCase() : undefined;
302
- }
303
- function checkRequiredTsOptions(compilerOptions) {
304
- const issues = [];
305
- const ok = [];
306
- const target = normalizeStr(compilerOptions?.target);
307
- const moduleVal = normalizeStr(compilerOptions?.module);
308
- const edm = compilerOptions?.emitDecoratorMetadata;
309
- const ed = compilerOptions?.experimentalDecorators;
310
- if (target === REQUIRED_DECORATOR_FIELDS.target)
311
- ok.push(`compilerOptions.target = "${REQUIRED_DECORATOR_FIELDS.target}"`);
312
- else
313
- issues.push(`compilerOptions.target should be "${REQUIRED_DECORATOR_FIELDS.target}"`);
314
- if (moduleVal === REQUIRED_DECORATOR_FIELDS.module)
315
- ok.push(`compilerOptions.module = "${REQUIRED_DECORATOR_FIELDS.module}"`);
316
- else
317
- issues.push(`compilerOptions.module should be "${REQUIRED_DECORATOR_FIELDS.module}"`);
318
- if (edm === REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata)
319
- ok.push(`compilerOptions.emitDecoratorMetadata = true`);
320
- else
321
- issues.push(`compilerOptions.emitDecoratorMetadata should be true`);
322
- if (ed === REQUIRED_DECORATOR_FIELDS.experimentalDecorators)
323
- ok.push(`compilerOptions.experimentalDecorators = true`);
324
- else
325
- issues.push(`compilerOptions.experimentalDecorators should be true`);
326
- return { ok, issues };
327
- }
328
- async function runInit(baseDir) {
329
- const cwd = baseDir ?? process.cwd();
330
- const tsconfigPath = path.join(cwd, 'tsconfig.json');
331
- const existing = await readJSON(tsconfigPath);
332
- if (!existing) {
333
- console.log(c('yellow', `tsconfig.json not found — creating one in ${path.relative(process.cwd(), cwd) || '.'}.`));
334
- await writeJSON(tsconfigPath, RECOMMENDED_TSCONFIG);
335
- console.log(c('green', '✅ Created tsconfig.json with required decorator settings.'));
336
- return;
337
- }
338
- let merged = deepMerge(RECOMMENDED_TSCONFIG, existing);
339
- merged = ensureRequiredTsOptions(merged);
340
- await writeJSON(tsconfigPath, merged);
341
- console.log(c('green', '✅ tsconfig.json verified and updated (required decorator settings enforced).'));
342
- }
343
- function cmpSemver(a, b) {
344
- const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
345
- const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
346
- for (let i = 0; i < 3; i++) {
347
- if ((pa[i] || 0) > (pb[i] || 0))
348
- return 1;
349
- if ((pa[i] || 0) < (pb[i] || 0))
350
- return -1;
351
- }
352
- return 0;
353
- }
354
- async function runDoctor() {
355
- const MIN_NODE = '22.0.0';
356
- const MIN_NPM = '10.0.0';
357
- const cwd = process.cwd();
358
- let ok = true;
359
- const nodeVer = process.versions.node;
360
- if (cmpSemver(nodeVer, MIN_NODE) >= 0) {
361
- console.log(`✅ Node ${nodeVer} (min ${MIN_NODE})`);
362
- }
363
- else {
364
- ok = false;
365
- console.log(`❌ Node ${nodeVer} — please upgrade to >= ${MIN_NODE}`);
366
- }
367
- let npmVer = 'unknown';
368
- try {
369
- npmVer = await new Promise((resolve, reject) => {
370
- const child = (0, child_process_1.spawn)('npm', ['-v'], { shell: true });
371
- let out = '';
372
- child.stdout?.on('data', (d) => (out += String(d)));
373
- child.on('close', () => resolve(out.trim()));
374
- child.on('error', reject);
375
- });
376
- if (cmpSemver(npmVer, MIN_NPM) >= 0) {
377
- console.log(`✅ npm ${npmVer} (min ${MIN_NPM})`);
378
- }
379
- else {
380
- ok = false;
381
- console.log(`❌ npm ${npmVer} — please upgrade to >= ${MIN_NPM}`);
382
- }
383
- }
384
- catch {
385
- ok = false;
386
- console.log('❌ npm not found in PATH');
387
- }
388
- const tsconfigPath = path.join(cwd, 'tsconfig.json');
389
- if (await fileExists(tsconfigPath)) {
390
- console.log(`✅ tsconfig.json found`);
391
- const tsconfig = await readJSON(tsconfigPath);
392
- const { ok: oks, issues } = checkRequiredTsOptions(tsconfig?.compilerOptions);
393
- for (const line of oks)
394
- console.log(c('green', ` ✓ ${line}`));
395
- if (issues.length) {
396
- ok = false;
397
- for (const line of issues)
398
- console.log(c('yellow', ` • ${line}`));
399
- console.log(c('cyan', ` -> Run "frontmcp init" to apply the required settings.`));
400
- }
401
- }
402
- else {
403
- ok = false;
404
- console.log(`❌ tsconfig.json not found — run ${c('cyan', 'frontmcp init')}`);
405
- }
406
- try {
407
- const entry = await resolveEntry(cwd);
408
- console.log(`✅ entry detected: ${path.relative(cwd, entry)}`);
409
- }
410
- catch (e) {
411
- const firstLine = e?.message?.split('\n')?.[0] ?? 'entry not found';
412
- console.log(`❌ entry not detected — ${firstLine}`);
413
- }
414
- if (ok)
415
- console.log(c('green', '\nAll checks passed. You are ready to go!'));
416
- else
417
- console.log(c('yellow', '\nSome checks failed. See above for fixes.'));
418
- }
419
- /* ------------------------------- Inspector -------------------------------- */
420
- async function runInspector() {
421
- console.log(`${c('cyan', '[inspector]')} launching MCP Inspector...`);
422
- await runCmd('npx', ['-y', '@modelcontextprotocol/inspector']);
423
- }
424
- /* --------------------------------- Create --------------------------------- */
425
- function pkgNameFromCwd(cwd) {
426
- return (path
427
- .basename(cwd)
428
- .replace(/[^a-zA-Z0-9._-]/g, '-')
429
- .toLowerCase() || 'frontmcp-app');
430
- }
431
- async function upsertPackageJson(cwd, nameOverride, selfVersion) {
432
- const pkgPath = path.join(cwd, 'package.json');
433
- const existing = await readJSON(pkgPath);
434
- // Use caret range for libs to track the CLI version
435
- const frontmcpLibRange = `^${selfVersion}`;
436
- const base = {
437
- name: nameOverride ?? pkgNameFromCwd(cwd),
438
- version: '0.1.0',
439
- private: true,
440
- type: 'commonjs',
441
- main: 'src/main.ts',
442
- scripts: {
443
- dev: 'frontmcp dev',
444
- build: 'frontmcp build',
445
- inspect: 'frontmcp inspector',
446
- doctor: 'frontmcp doctor',
447
- },
448
- engines: {
449
- node: '>=22',
450
- npm: '>=10',
451
- },
452
- dependencies: {
453
- '@frontmcp/sdk': frontmcpLibRange,
454
- '@frontmcp/plugins': frontmcpLibRange,
455
- '@frontmcp/adapters': frontmcpLibRange,
456
- zod: '^3.23.8',
457
- 'reflect-metadata': '^0.2.2',
458
- },
459
- devDependencies: {
460
- frontmcp: selfVersion, // exact CLI version used by npx
461
- tsx: '^4.20.6',
462
- '@types/node': '^22.0.0',
463
- typescript: '^5.5.3',
464
- },
465
- };
466
- if (!existing) {
467
- await writeJSON(pkgPath, base);
468
- console.log(c('green', '✅ Created package.json (synced @frontmcp libs to CLI version + exact frontmcp)'));
469
- return;
470
- }
471
- const merged = { ...base, ...existing };
472
- // Preserve some user fields if present
473
- merged.name = existing.name || base.name;
474
- merged.main = existing.main || base.main;
475
- merged.type = existing.type || base.type;
476
- merged.scripts = {
477
- ...base.scripts,
478
- ...(existing.scripts || {}),
479
- dev: existing.scripts?.dev ?? base.scripts.dev,
480
- build: existing.scripts?.build ?? base.scripts.build,
481
- inspect: existing.scripts?.inspect ?? base.scripts.inspect,
482
- doctor: existing.scripts?.doctor ?? base.scripts.doctor,
483
- };
484
- merged.engines = {
485
- ...(existing.engines || {}),
486
- node: existing.engines?.node || base.engines.node,
487
- npm: existing.engines?.npm || base.engines.npm,
488
- };
489
- // Force @frontmcp libs to follow the CLI version range
490
- merged.dependencies = {
491
- ...(existing.dependencies || {}),
492
- ...base.dependencies,
493
- '@frontmcp/sdk': frontmcpLibRange,
494
- '@frontmcp/plugins': frontmcpLibRange,
495
- '@frontmcp/adapters': frontmcpLibRange,
496
- zod: '^3.23.8',
497
- 'reflect-metadata': '^0.2.2',
498
- };
499
- merged.devDependencies = {
500
- ...(existing.devDependencies || {}),
501
- ...base.devDependencies,
502
- frontmcp: selfVersion,
503
- tsx: '^4.20.6',
504
- typescript: '^5.5.3',
505
- };
506
- await writeJSON(pkgPath, merged);
507
- console.log(c('green', '✅ Updated package.json (synced @frontmcp libs + frontmcp to current CLI version)'));
508
- }
509
- async function scaffoldFileIfMissing(baseDir, p, content) {
510
- if (await fileExists(p)) {
511
- console.log(c('gray', `skip: ${path.relative(baseDir, p)} already exists`));
512
- return;
513
- }
514
- await ensureDir(path.dirname(p));
515
- await fs_1.promises.writeFile(p, content.replace(/^\n/, ''), 'utf8');
516
- console.log(c('green', `✓ created ${path.relative(baseDir, p)}`));
517
- }
518
- const TEMPLATE_MAIN_TS = `
519
- import 'reflect-metadata';
520
- import { FrontMcp } from '@frontmcp/sdk';
521
- import { CalcApp } from './calc.app';
522
-
523
- @FrontMcp({
524
- info: { name: 'Demo 🚀', version: '0.1.0' },
525
- apps: [CalcApp],
526
- })
527
- export default class Server {}
528
- `;
529
- const TEMPLATE_CALC_APP_TS = `
530
- import { App } from '@frontmcp/sdk';
531
- import AddTool from './tools/add.tool';
532
-
533
- @App({
534
- id: 'calc',
535
- name: 'Calculator',
536
- tools: [AddTool],
537
- })
538
- export class CalcApp {}
539
- `;
540
- const TEMPLATE_ADD_TOOL_TS = `
541
- import {Tool, ToolContext} from "@frontmcp/sdk";
542
- import {z} from "zod";
543
-
544
- @Tool({
545
- name: 'add',
546
- description: 'Add two numbers',
547
- inputSchema: {a: z.number(), b: z.number()},
548
- outputSchema: {result: z.number()}
549
- })
550
- export default class AddTool extends ToolContext {
551
- async execute(input: { a: number, b: number }) {
552
- return {
553
- result: input.a + input.b,
554
- };
555
- }
556
- }
557
- `;
558
- async function runCreate(projectArg) {
559
- if (!projectArg) {
560
- console.error(c('red', 'Error: project name is required.\n'));
561
- console.log(`Usage: ${c('bold', 'npx frontmcp create <project-name>')}`);
562
- process.exit(1);
563
- }
564
- const folder = sanitizeForFolder(projectArg);
565
- const pkgName = sanitizeForNpm(projectArg);
566
- const targetDir = path.resolve(process.cwd(), folder);
567
- if (await fileExists(targetDir)) {
568
- if (!(await isDirEmpty(targetDir))) {
569
- console.error(c('red', `Refusing to scaffold into non-empty directory: ${path.relative(process.cwd(), targetDir)}`));
570
- console.log(c('gray', 'Pick a different name or start with an empty folder.'));
571
- process.exit(1);
572
- }
573
- }
574
- else {
575
- await ensureDir(targetDir);
576
- }
577
- console.log(`${c('cyan', '[create]')} Creating project in ${c('bold', './' + path.relative(process.cwd(), targetDir))}`);
578
- process.chdir(targetDir);
579
- // 1) tsconfig
580
- await runInit(targetDir);
581
- // 2) package.json (pinned deps + exact CLI version)
582
- const selfVersion = (0, version_1.getSelfVersion)();
583
- await upsertPackageJson(targetDir, pkgName, selfVersion);
584
- // 3) files
585
- await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'main.ts'), TEMPLATE_MAIN_TS);
586
- await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'calc.app.ts'), TEMPLATE_CALC_APP_TS);
587
- await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'tools', 'add.tool.ts'), TEMPLATE_ADD_TOOL_TS);
588
- console.log('\nNext steps:');
589
- console.log(` 1) cd ${folder}`);
590
- console.log(' 2) npm install');
591
- console.log(' 3) npm run dev ', c('gray', '# tsx watcher + async tsc type-check'));
592
- console.log(' 4) npm run inspect ', c('gray', '# launch MCP Inspector'));
593
- console.log(' 5) npm run build ', c('gray', '# compile with tsc via frontmcp build'));
594
- }
595
- /* --------------------------------- Main ----------------------------------- */
596
47
  async function main() {
597
48
  const argv = process.argv.slice(2);
598
- const parsed = parseArgs(argv);
49
+ const parsed = (0, args_1.parseArgs)(argv);
599
50
  const cmd = parsed._[0];
600
51
  if (parsed.help || !cmd) {
601
52
  showHelp();
@@ -604,37 +55,42 @@ async function main() {
604
55
  try {
605
56
  switch (cmd) {
606
57
  case 'dev':
607
- await runDev(parsed);
58
+ await (0, dev_1.runDev)(parsed);
608
59
  break;
609
60
  case 'build':
610
61
  parsed.outDir = parsed.outDir || 'dist';
611
- await runBuild(parsed);
62
+ await (0, build_1.runBuild)(parsed);
612
63
  break;
613
64
  case 'init':
614
- await runInit();
65
+ await (0, tsconfig_1.runInit)();
615
66
  break;
616
67
  case 'doctor':
617
- await runDoctor();
68
+ await (0, doctor_1.runDoctor)();
618
69
  break;
619
70
  case 'inspector':
620
- await runInspector();
71
+ await (0, inspector_1.runInspector)();
621
72
  break;
622
73
  case 'create': {
623
74
  const projectName = parsed._[1];
624
- await runCreate(projectName);
75
+ await (0, create_1.runCreate)(projectName);
76
+ break;
77
+ }
78
+ case 'template': {
79
+ const type = parsed._[1]; // e.g. "3rd-party-integration"
80
+ await (0, template_1.runTemplate)(type);
625
81
  break;
626
82
  }
627
83
  case 'help':
628
84
  showHelp();
629
85
  break;
630
86
  default:
631
- console.error(c('red', `Unknown command: ${cmd}`));
87
+ console.error((0, colors_1.c)('red', `Unknown command: ${cmd}`));
632
88
  showHelp();
633
89
  process.exitCode = 1;
634
90
  }
635
91
  }
636
92
  catch (err) {
637
- console.error('\n' + c('red', err instanceof Error ? err.stack || err.message : String(err)));
93
+ console.error('\n' + (0, colors_1.c)('red', err instanceof Error ? err.stack || err.message : String(err)));
638
94
  process.exit(1);
639
95
  }
640
96
  }