frontmcp 0.1.2 → 0.1.3
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/dist/cli.js +217 -14
- package/package.json +7 -6
- package/src/cli.ts +257 -15
package/dist/cli.js
CHANGED
|
@@ -74,7 +74,9 @@ ${c('bold', 'Commands')}
|
|
|
74
74
|
dev Start in development mode (tsx --watch <entry>)
|
|
75
75
|
build Compile entry with TypeScript (tsc)
|
|
76
76
|
init Create or fix a tsconfig.json suitable for FrontMCP
|
|
77
|
-
doctor Check Node/npm versions and tsconfig
|
|
77
|
+
doctor Check Node/npm versions and tsconfig requirements
|
|
78
|
+
inspector Launch MCP Inspector (npx @modelcontextprotocol/inspector)
|
|
79
|
+
create Scaffold a new FrontMCP project in the current directory
|
|
78
80
|
help Show this help message
|
|
79
81
|
|
|
80
82
|
${c('bold', 'Options')}
|
|
@@ -86,6 +88,8 @@ ${c('bold', 'Examples')}
|
|
|
86
88
|
frontmcp build --out-dir build
|
|
87
89
|
frontmcp init
|
|
88
90
|
frontmcp doctor
|
|
91
|
+
frontmcp inspector
|
|
92
|
+
npx frontmcp create
|
|
89
93
|
`);
|
|
90
94
|
}
|
|
91
95
|
function parseArgs(argv) {
|
|
@@ -125,6 +129,11 @@ function readJSON(jsonPath) {
|
|
|
125
129
|
}
|
|
126
130
|
});
|
|
127
131
|
}
|
|
132
|
+
function writeJSON(p, obj) {
|
|
133
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
yield fs_1.promises.writeFile(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
135
|
+
});
|
|
136
|
+
}
|
|
128
137
|
function tryCandidates(base) {
|
|
129
138
|
const exts = ['', '.ts', '.tsx', '.js', '.mjs', '.cjs'];
|
|
130
139
|
return exts.map((ext) => base + ext);
|
|
@@ -226,11 +235,20 @@ function runBuild(opts) {
|
|
|
226
235
|
console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));
|
|
227
236
|
});
|
|
228
237
|
}
|
|
238
|
+
/* --------------------------- tsconfig management --------------------------- */
|
|
239
|
+
const REQUIRED_DECORATOR_FIELDS = {
|
|
240
|
+
target: 'es2021',
|
|
241
|
+
module: 'esnext',
|
|
242
|
+
emitDecoratorMetadata: true,
|
|
243
|
+
experimentalDecorators: true,
|
|
244
|
+
};
|
|
229
245
|
const RECOMMENDED_TSCONFIG = {
|
|
230
246
|
compilerOptions: {
|
|
231
|
-
target:
|
|
232
|
-
module:
|
|
233
|
-
|
|
247
|
+
target: REQUIRED_DECORATOR_FIELDS.target,
|
|
248
|
+
module: REQUIRED_DECORATOR_FIELDS.module,
|
|
249
|
+
emitDecoratorMetadata: REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata,
|
|
250
|
+
experimentalDecorators: REQUIRED_DECORATOR_FIELDS.experimentalDecorators,
|
|
251
|
+
moduleResolution: 'NodeNext',
|
|
234
252
|
strict: true,
|
|
235
253
|
esModuleInterop: true,
|
|
236
254
|
resolveJsonModule: true,
|
|
@@ -255,6 +273,44 @@ function deepMerge(base, patch) {
|
|
|
255
273
|
}
|
|
256
274
|
return out;
|
|
257
275
|
}
|
|
276
|
+
function ensureRequiredTsOptions(obj) {
|
|
277
|
+
const next = Object.assign({}, obj);
|
|
278
|
+
next.compilerOptions = Object.assign({}, (next.compilerOptions || {}));
|
|
279
|
+
// Force the required values
|
|
280
|
+
next.compilerOptions.target = REQUIRED_DECORATOR_FIELDS.target;
|
|
281
|
+
next.compilerOptions.module = REQUIRED_DECORATOR_FIELDS.module;
|
|
282
|
+
next.compilerOptions.emitDecoratorMetadata = REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata;
|
|
283
|
+
next.compilerOptions.experimentalDecorators = REQUIRED_DECORATOR_FIELDS.experimentalDecorators;
|
|
284
|
+
return next;
|
|
285
|
+
}
|
|
286
|
+
function normalizeStr(x) {
|
|
287
|
+
return typeof x === 'string' ? x.toLowerCase() : undefined;
|
|
288
|
+
}
|
|
289
|
+
function checkRequiredTsOptions(compilerOptions) {
|
|
290
|
+
const issues = [];
|
|
291
|
+
const ok = [];
|
|
292
|
+
const target = normalizeStr(compilerOptions === null || compilerOptions === void 0 ? void 0 : compilerOptions.target);
|
|
293
|
+
const moduleVal = normalizeStr(compilerOptions === null || compilerOptions === void 0 ? void 0 : compilerOptions.module);
|
|
294
|
+
const edm = compilerOptions === null || compilerOptions === void 0 ? void 0 : compilerOptions.emitDecoratorMetadata;
|
|
295
|
+
const ed = compilerOptions === null || compilerOptions === void 0 ? void 0 : compilerOptions.experimentalDecorators;
|
|
296
|
+
if (target === REQUIRED_DECORATOR_FIELDS.target)
|
|
297
|
+
ok.push(`compilerOptions.target = "${REQUIRED_DECORATOR_FIELDS.target}"`);
|
|
298
|
+
else
|
|
299
|
+
issues.push(`compilerOptions.target should be "${REQUIRED_DECORATOR_FIELDS.target}"`);
|
|
300
|
+
if (moduleVal === REQUIRED_DECORATOR_FIELDS.module)
|
|
301
|
+
ok.push(`compilerOptions.module = "${REQUIRED_DECORATOR_FIELDS.module}"`);
|
|
302
|
+
else
|
|
303
|
+
issues.push(`compilerOptions.module should be "${REQUIRED_DECORATOR_FIELDS.module}"`);
|
|
304
|
+
if (edm === REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata)
|
|
305
|
+
ok.push(`compilerOptions.emitDecoratorMetadata = true`);
|
|
306
|
+
else
|
|
307
|
+
issues.push(`compilerOptions.emitDecoratorMetadata should be true`);
|
|
308
|
+
if (ed === REQUIRED_DECORATOR_FIELDS.experimentalDecorators)
|
|
309
|
+
ok.push(`compilerOptions.experimentalDecorators = true`);
|
|
310
|
+
else
|
|
311
|
+
issues.push(`compilerOptions.experimentalDecorators should be true`);
|
|
312
|
+
return { ok, issues };
|
|
313
|
+
}
|
|
258
314
|
function runInit() {
|
|
259
315
|
return __awaiter(this, void 0, void 0, function* () {
|
|
260
316
|
const cwd = process.cwd();
|
|
@@ -262,15 +318,15 @@ function runInit() {
|
|
|
262
318
|
const existing = yield readJSON(tsconfigPath);
|
|
263
319
|
if (!existing) {
|
|
264
320
|
console.log(c('yellow', 'tsconfig.json not found — creating one.'));
|
|
265
|
-
yield
|
|
266
|
-
console.log(c('green', '✅ Created tsconfig.json'));
|
|
321
|
+
yield writeJSON(tsconfigPath, RECOMMENDED_TSCONFIG);
|
|
322
|
+
console.log(c('green', '✅ Created tsconfig.json with required decorator settings.'));
|
|
267
323
|
return;
|
|
268
324
|
}
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
yield
|
|
273
|
-
console.log(c('green', '✅ tsconfig.json verified
|
|
325
|
+
// Merge user config on top of recommended, then force required decorator options
|
|
326
|
+
let merged = deepMerge(RECOMMENDED_TSCONFIG, existing);
|
|
327
|
+
merged = ensureRequiredTsOptions(merged);
|
|
328
|
+
yield writeJSON(tsconfigPath, merged);
|
|
329
|
+
console.log(c('green', '✅ tsconfig.json verified and updated (required decorator settings enforced).'));
|
|
274
330
|
});
|
|
275
331
|
}
|
|
276
332
|
function cmpSemver(a, b) {
|
|
@@ -287,8 +343,8 @@ function cmpSemver(a, b) {
|
|
|
287
343
|
function runDoctor() {
|
|
288
344
|
return __awaiter(this, void 0, void 0, function* () {
|
|
289
345
|
var _a, _b, _c;
|
|
290
|
-
const MIN_NODE = '
|
|
291
|
-
const MIN_NPM = '
|
|
346
|
+
const MIN_NODE = '22.0.0';
|
|
347
|
+
const MIN_NPM = '10.0.0';
|
|
292
348
|
const cwd = process.cwd();
|
|
293
349
|
let ok = true;
|
|
294
350
|
// Node
|
|
@@ -323,10 +379,20 @@ function runDoctor() {
|
|
|
323
379
|
ok = false;
|
|
324
380
|
console.log('❌ npm not found in PATH');
|
|
325
381
|
}
|
|
326
|
-
// tsconfig.json presence
|
|
382
|
+
// tsconfig.json presence + required fields
|
|
327
383
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
328
384
|
if (yield fileExists(tsconfigPath)) {
|
|
329
385
|
console.log(`✅ tsconfig.json found`);
|
|
386
|
+
const tsconfig = yield readJSON(tsconfigPath);
|
|
387
|
+
const { ok: oks, issues } = checkRequiredTsOptions(tsconfig === null || tsconfig === void 0 ? void 0 : tsconfig.compilerOptions);
|
|
388
|
+
for (const line of oks)
|
|
389
|
+
console.log(c('green', ` ✓ ${line}`));
|
|
390
|
+
if (issues.length) {
|
|
391
|
+
ok = false;
|
|
392
|
+
for (const line of issues)
|
|
393
|
+
console.log(c('yellow', ` • ${line}`));
|
|
394
|
+
console.log(c('cyan', ` -> Run "frontmcp init" to apply the required settings.`));
|
|
395
|
+
}
|
|
330
396
|
}
|
|
331
397
|
else {
|
|
332
398
|
ok = false;
|
|
@@ -349,6 +415,137 @@ function runDoctor() {
|
|
|
349
415
|
}
|
|
350
416
|
});
|
|
351
417
|
}
|
|
418
|
+
/* ------------------------------- Inspector -------------------------------- */
|
|
419
|
+
function runInspector() {
|
|
420
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
421
|
+
console.log(`${c('cyan', '[inspector]')} launching MCP Inspector...`);
|
|
422
|
+
yield runCmd('npx', ['-y', '@modelcontextprotocol/inspector']);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/* --------------------------------- Create --------------------------------- */
|
|
426
|
+
function pkgNameFromCwd(cwd) {
|
|
427
|
+
return path.basename(cwd).replace(/[^a-zA-Z0-9._-]/g, '-').toLowerCase() || 'frontmcp-app';
|
|
428
|
+
}
|
|
429
|
+
function upsertPackageJson(cwd) {
|
|
430
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
431
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
432
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
433
|
+
const existing = yield readJSON(pkgPath);
|
|
434
|
+
const base = {
|
|
435
|
+
name: pkgNameFromCwd(cwd),
|
|
436
|
+
version: '0.1.0',
|
|
437
|
+
private: true,
|
|
438
|
+
type: 'module',
|
|
439
|
+
main: 'src/main.ts',
|
|
440
|
+
scripts: {
|
|
441
|
+
dev: 'frontmcp dev',
|
|
442
|
+
build: 'frontmcp build',
|
|
443
|
+
inspect: 'frontmcp inspector',
|
|
444
|
+
doctor: 'frontmcp doctor',
|
|
445
|
+
},
|
|
446
|
+
engines: {
|
|
447
|
+
node: '>=22',
|
|
448
|
+
npm: '>=10',
|
|
449
|
+
},
|
|
450
|
+
dependencies: {
|
|
451
|
+
'@frontmcp/sdk': 'latest',
|
|
452
|
+
zod: 'latest',
|
|
453
|
+
},
|
|
454
|
+
devDependencies: {
|
|
455
|
+
typescript: 'latest',
|
|
456
|
+
tsx: 'latest',
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
if (!existing) {
|
|
460
|
+
yield writeJSON(pkgPath, base);
|
|
461
|
+
console.log(c('green', '✅ Created package.json (with scripts: dev, build, inspect, doctor)'));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
// Merge, preserving user fields; ensure our requirements exist
|
|
465
|
+
const merged = Object.assign(Object.assign({}, base), existing);
|
|
466
|
+
merged.main = existing.main || base.main;
|
|
467
|
+
merged.type = existing.type || base.type;
|
|
468
|
+
merged.scripts = Object.assign(Object.assign(Object.assign({}, base.scripts), (existing.scripts || {})), { dev: ((_b = (_a = existing.scripts) === null || _a === void 0 ? void 0 : _a.dev) !== null && _b !== void 0 ? _b : base.scripts.dev), build: ((_d = (_c = existing.scripts) === null || _c === void 0 ? void 0 : _c.build) !== null && _d !== void 0 ? _d : base.scripts.build), inspect: ((_f = (_e = existing.scripts) === null || _e === void 0 ? void 0 : _e.inspect) !== null && _f !== void 0 ? _f : base.scripts.inspect), doctor: ((_h = (_g = existing.scripts) === null || _g === void 0 ? void 0 : _g.doctor) !== null && _h !== void 0 ? _h : base.scripts.doctor) });
|
|
469
|
+
merged.engines = Object.assign(Object.assign({}, (existing.engines || {})), { node: ((_j = existing.engines) === null || _j === void 0 ? void 0 : _j.node) || base.engines.node, npm: ((_k = existing.engines) === null || _k === void 0 ? void 0 : _k.npm) || base.engines.npm });
|
|
470
|
+
merged.dependencies = Object.assign(Object.assign({}, existing.dependencies), { '@frontmcp/sdk': ((_l = existing.dependencies) === null || _l === void 0 ? void 0 : _l['@frontmcp/sdk']) || base.dependencies['@frontmcp/sdk'], zod: ((_m = existing.dependencies) === null || _m === void 0 ? void 0 : _m.zod) || base.dependencies.zod });
|
|
471
|
+
merged.devDependencies = Object.assign(Object.assign({}, existing.devDependencies), { typescript: ((_o = existing.devDependencies) === null || _o === void 0 ? void 0 : _o.typescript) || base.devDependencies.typescript, tsx: ((_p = existing.devDependencies) === null || _p === void 0 ? void 0 : _p.tsx) || base.devDependencies.tsx });
|
|
472
|
+
yield writeJSON(pkgPath, merged);
|
|
473
|
+
console.log(c('green', '✅ Updated package.json (ensured scripts, engines, deps)'));
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function scaffoldFileIfMissing(p, content) {
|
|
477
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
478
|
+
if (yield fileExists(p)) {
|
|
479
|
+
console.log(c('gray', `skip: ${path.relative(process.cwd(), p)} already exists`));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
yield ensureDir(path.dirname(p));
|
|
483
|
+
yield fs_1.promises.writeFile(p, content.replace(/^\n/, ''), 'utf8');
|
|
484
|
+
console.log(c('green', `✓ created ${path.relative(process.cwd(), p)}`));
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const TEMPLATE_MAIN_TS = `
|
|
488
|
+
import 'reflect-metadata';
|
|
489
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
490
|
+
import { CalcApp } from './calc.app';
|
|
491
|
+
|
|
492
|
+
@FrontMcp({
|
|
493
|
+
info: { name: 'Demo 🚀', version: '0.1.0' },
|
|
494
|
+
apps: [CalcApp],
|
|
495
|
+
auth: {
|
|
496
|
+
type: 'remote',
|
|
497
|
+
name: 'my-remote-auth',
|
|
498
|
+
baseUrl: 'https://idp.example.com',
|
|
499
|
+
},
|
|
500
|
+
})
|
|
501
|
+
export default class Server {}
|
|
502
|
+
`;
|
|
503
|
+
const TEMPLATE_CALC_APP_TS = `
|
|
504
|
+
import { App } from '@frontmcp/sdk';
|
|
505
|
+
import AddTool from './tools/add.tool';
|
|
506
|
+
|
|
507
|
+
@App({
|
|
508
|
+
id: 'calc',
|
|
509
|
+
name: 'Calculator',
|
|
510
|
+
tools: [AddTool],
|
|
511
|
+
})
|
|
512
|
+
export class CalcApp {}
|
|
513
|
+
`;
|
|
514
|
+
const TEMPLATE_ADD_TOOL_TS = `
|
|
515
|
+
import { tool } from '@frontmcp/sdk';
|
|
516
|
+
import { z } from 'zod';
|
|
517
|
+
|
|
518
|
+
const AddTool = tool({
|
|
519
|
+
name: 'add',
|
|
520
|
+
description: 'Add two numbers',
|
|
521
|
+
inputSchema: z.object({ a: z.number(), b: z.number() }),
|
|
522
|
+
outputSchema: z.object({ result: z.number() }),
|
|
523
|
+
})((input, _ctx) => {
|
|
524
|
+
return { result: input.a + input.b };
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
export default AddTool;
|
|
528
|
+
`;
|
|
529
|
+
function runCreate() {
|
|
530
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
531
|
+
const cwd = process.cwd();
|
|
532
|
+
console.log(c('cyan', '[create]'), 'Scaffolding FrontMCP project in', c('bold', cwd));
|
|
533
|
+
// 1) tsconfig
|
|
534
|
+
yield runInit();
|
|
535
|
+
// 2) package.json
|
|
536
|
+
yield upsertPackageJson(cwd);
|
|
537
|
+
// 3) files
|
|
538
|
+
yield scaffoldFileIfMissing(path.join(cwd, 'src', 'main.ts'), TEMPLATE_MAIN_TS);
|
|
539
|
+
yield scaffoldFileIfMissing(path.join(cwd, 'src', 'calc.app.ts'), TEMPLATE_CALC_APP_TS);
|
|
540
|
+
yield scaffoldFileIfMissing(path.join(cwd, 'src', 'tools', 'add.tool.ts'), TEMPLATE_ADD_TOOL_TS);
|
|
541
|
+
// 4) final tips
|
|
542
|
+
console.log('\nNext steps:');
|
|
543
|
+
console.log(' 1) npm install');
|
|
544
|
+
console.log(' 2) npm run dev ', c('gray', '# starts tsx watcher via frontmcp dev'));
|
|
545
|
+
console.log(' 3) npm run inspect', c('gray', '# launch MCP Inspector'));
|
|
546
|
+
console.log(' 4) npm run build ', c('gray', '# compile with tsc via frontmcp build'));
|
|
547
|
+
});
|
|
548
|
+
}
|
|
352
549
|
/* --------------------------------- Main ----------------------------------- */
|
|
353
550
|
function main() {
|
|
354
551
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -374,6 +571,12 @@ function main() {
|
|
|
374
571
|
case 'doctor':
|
|
375
572
|
yield runDoctor();
|
|
376
573
|
break;
|
|
574
|
+
case 'inspector':
|
|
575
|
+
yield runInspector();
|
|
576
|
+
break;
|
|
577
|
+
case 'create':
|
|
578
|
+
yield runCreate();
|
|
579
|
+
break;
|
|
377
580
|
case 'help':
|
|
378
581
|
showHelp();
|
|
379
582
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontmcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "FrontMCP command line interface",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
"prepare": "npm run build"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@frontmcp/sdk": "0.1.
|
|
16
|
-
"@frontmcp/core": "0.1.
|
|
17
|
-
"@frontmcp/plugins": "0.1.
|
|
18
|
-
"@frontmcp/adapters": "0.1.
|
|
15
|
+
"@frontmcp/sdk": "0.1.3",
|
|
16
|
+
"@frontmcp/core": "0.1.3",
|
|
17
|
+
"@frontmcp/plugins": "0.1.3",
|
|
18
|
+
"@frontmcp/adapters": "0.1.3",
|
|
19
19
|
"tsx": "^4.20.6",
|
|
20
20
|
"typescript": "^5.5.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/node": "20.19.9"
|
|
23
|
+
"@types/node": "20.19.9",
|
|
24
|
+
"@modelcontextprotocol/inspector": "^0.17.2"
|
|
24
25
|
}
|
|
25
26
|
}
|
package/src/cli.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {spawn} from 'child_process';
|
|
|
11
11
|
|
|
12
12
|
/* ----------------------------- Types & Helpers ---------------------------- */
|
|
13
13
|
|
|
14
|
-
type Command = 'dev' | 'build' | 'init' | 'doctor' | 'help';
|
|
14
|
+
type Command = 'dev' | 'build' | 'init' | 'doctor' | 'inspector' | 'create' | 'help';
|
|
15
15
|
|
|
16
16
|
interface ParsedArgs {
|
|
17
17
|
_: string[];
|
|
@@ -45,7 +45,9 @@ ${c('bold', 'Commands')}
|
|
|
45
45
|
dev Start in development mode (tsx --watch <entry>)
|
|
46
46
|
build Compile entry with TypeScript (tsc)
|
|
47
47
|
init Create or fix a tsconfig.json suitable for FrontMCP
|
|
48
|
-
doctor Check Node/npm versions and tsconfig
|
|
48
|
+
doctor Check Node/npm versions and tsconfig requirements
|
|
49
|
+
inspector Launch MCP Inspector (npx @modelcontextprotocol/inspector)
|
|
50
|
+
create Scaffold a new FrontMCP project in the current directory
|
|
49
51
|
help Show this help message
|
|
50
52
|
|
|
51
53
|
${c('bold', 'Options')}
|
|
@@ -57,6 +59,8 @@ ${c('bold', 'Examples')}
|
|
|
57
59
|
frontmcp build --out-dir build
|
|
58
60
|
frontmcp init
|
|
59
61
|
frontmcp doctor
|
|
62
|
+
frontmcp inspector
|
|
63
|
+
npx frontmcp create
|
|
60
64
|
`);
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -90,6 +94,10 @@ async function readJSON<T = any>(jsonPath: string): Promise<T | null> {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
async function writeJSON(p: string, obj: any) {
|
|
98
|
+
await fsp.writeFile(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
function tryCandidates(base: string): string[] {
|
|
94
102
|
const exts = ['', '.ts', '.tsx', '.js', '.mjs', '.cjs'];
|
|
95
103
|
return exts.map((ext) => base + ext);
|
|
@@ -195,11 +203,23 @@ async function runBuild(opts: ParsedArgs): Promise<void> {
|
|
|
195
203
|
console.log(c('gray', `Output placed in ${path.relative(cwd, outDir)}`));
|
|
196
204
|
}
|
|
197
205
|
|
|
206
|
+
/* --------------------------- tsconfig management --------------------------- */
|
|
207
|
+
|
|
208
|
+
const REQUIRED_DECORATOR_FIELDS = {
|
|
209
|
+
target: 'es2021',
|
|
210
|
+
module: 'esnext',
|
|
211
|
+
emitDecoratorMetadata: true,
|
|
212
|
+
experimentalDecorators: true,
|
|
213
|
+
} as const;
|
|
214
|
+
|
|
198
215
|
const RECOMMENDED_TSCONFIG = {
|
|
199
216
|
compilerOptions: {
|
|
200
|
-
target:
|
|
201
|
-
module:
|
|
202
|
-
|
|
217
|
+
target: REQUIRED_DECORATOR_FIELDS.target,
|
|
218
|
+
module: REQUIRED_DECORATOR_FIELDS.module,
|
|
219
|
+
emitDecoratorMetadata: REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata,
|
|
220
|
+
experimentalDecorators: REQUIRED_DECORATOR_FIELDS.experimentalDecorators,
|
|
221
|
+
|
|
222
|
+
moduleResolution: 'NodeNext',
|
|
203
223
|
strict: true,
|
|
204
224
|
esModuleInterop: true,
|
|
205
225
|
resolveJsonModule: true,
|
|
@@ -224,6 +244,47 @@ function deepMerge<T extends Record<string, any>, U extends Record<string, any>>
|
|
|
224
244
|
return out as T & U;
|
|
225
245
|
}
|
|
226
246
|
|
|
247
|
+
function ensureRequiredTsOptions(obj: Record<string, any>): Record<string, any> {
|
|
248
|
+
const next = {...obj};
|
|
249
|
+
next.compilerOptions = {...(next.compilerOptions || {})};
|
|
250
|
+
|
|
251
|
+
// Force the required values
|
|
252
|
+
next.compilerOptions.target = REQUIRED_DECORATOR_FIELDS.target;
|
|
253
|
+
next.compilerOptions.module = REQUIRED_DECORATOR_FIELDS.module;
|
|
254
|
+
next.compilerOptions.emitDecoratorMetadata = REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata;
|
|
255
|
+
next.compilerOptions.experimentalDecorators = REQUIRED_DECORATOR_FIELDS.experimentalDecorators;
|
|
256
|
+
|
|
257
|
+
return next;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeStr(x: unknown): string | undefined {
|
|
261
|
+
return typeof x === 'string' ? x.toLowerCase() : undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function checkRequiredTsOptions(compilerOptions: Record<string, any> | undefined) {
|
|
265
|
+
const issues: string[] = [];
|
|
266
|
+
const ok: string[] = [];
|
|
267
|
+
|
|
268
|
+
const target = normalizeStr(compilerOptions?.target);
|
|
269
|
+
const moduleVal = normalizeStr(compilerOptions?.module);
|
|
270
|
+
const edm = compilerOptions?.emitDecoratorMetadata;
|
|
271
|
+
const ed = compilerOptions?.experimentalDecorators;
|
|
272
|
+
|
|
273
|
+
if (target === REQUIRED_DECORATOR_FIELDS.target) ok.push(`compilerOptions.target = "${REQUIRED_DECORATOR_FIELDS.target}"`);
|
|
274
|
+
else issues.push(`compilerOptions.target should be "${REQUIRED_DECORATOR_FIELDS.target}"`);
|
|
275
|
+
|
|
276
|
+
if (moduleVal === REQUIRED_DECORATOR_FIELDS.module) ok.push(`compilerOptions.module = "${REQUIRED_DECORATOR_FIELDS.module}"`);
|
|
277
|
+
else issues.push(`compilerOptions.module should be "${REQUIRED_DECORATOR_FIELDS.module}"`);
|
|
278
|
+
|
|
279
|
+
if (edm === REQUIRED_DECORATOR_FIELDS.emitDecoratorMetadata) ok.push(`compilerOptions.emitDecoratorMetadata = true`);
|
|
280
|
+
else issues.push(`compilerOptions.emitDecoratorMetadata should be true`);
|
|
281
|
+
|
|
282
|
+
if (ed === REQUIRED_DECORATOR_FIELDS.experimentalDecorators) ok.push(`compilerOptions.experimentalDecorators = true`);
|
|
283
|
+
else issues.push(`compilerOptions.experimentalDecorators should be true`);
|
|
284
|
+
|
|
285
|
+
return {ok, issues};
|
|
286
|
+
}
|
|
287
|
+
|
|
227
288
|
async function runInit(): Promise<void> {
|
|
228
289
|
const cwd = process.cwd();
|
|
229
290
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
@@ -231,16 +292,17 @@ async function runInit(): Promise<void> {
|
|
|
231
292
|
|
|
232
293
|
if (!existing) {
|
|
233
294
|
console.log(c('yellow', 'tsconfig.json not found — creating one.'));
|
|
234
|
-
await
|
|
235
|
-
console.log(c('green', '✅ Created tsconfig.json'));
|
|
295
|
+
await writeJSON(tsconfigPath, RECOMMENDED_TSCONFIG);
|
|
296
|
+
console.log(c('green', '✅ Created tsconfig.json with required decorator settings.'));
|
|
236
297
|
return;
|
|
237
298
|
}
|
|
238
299
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
300
|
+
// Merge user config on top of recommended, then force required decorator options
|
|
301
|
+
let merged = deepMerge(RECOMMENDED_TSCONFIG as any, existing);
|
|
302
|
+
merged = ensureRequiredTsOptions(merged);
|
|
303
|
+
|
|
304
|
+
await writeJSON(tsconfigPath, merged);
|
|
305
|
+
console.log(c('green', '✅ tsconfig.json verified and updated (required decorator settings enforced).'));
|
|
244
306
|
}
|
|
245
307
|
|
|
246
308
|
function cmpSemver(a: string, b: string): number {
|
|
@@ -254,8 +316,8 @@ function cmpSemver(a: string, b: string): number {
|
|
|
254
316
|
}
|
|
255
317
|
|
|
256
318
|
async function runDoctor(): Promise<void> {
|
|
257
|
-
const MIN_NODE = '
|
|
258
|
-
const MIN_NPM = '
|
|
319
|
+
const MIN_NODE = '22.0.0';
|
|
320
|
+
const MIN_NPM = '10.0.0';
|
|
259
321
|
const cwd = process.cwd();
|
|
260
322
|
|
|
261
323
|
let ok = true;
|
|
@@ -290,10 +352,19 @@ async function runDoctor(): Promise<void> {
|
|
|
290
352
|
console.log('❌ npm not found in PATH');
|
|
291
353
|
}
|
|
292
354
|
|
|
293
|
-
// tsconfig.json presence
|
|
355
|
+
// tsconfig.json presence + required fields
|
|
294
356
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
295
357
|
if (await fileExists(tsconfigPath)) {
|
|
296
358
|
console.log(`✅ tsconfig.json found`);
|
|
359
|
+
const tsconfig = await readJSON<Record<string, any>>(tsconfigPath);
|
|
360
|
+
const {ok: oks, issues} = checkRequiredTsOptions(tsconfig?.compilerOptions);
|
|
361
|
+
|
|
362
|
+
for (const line of oks) console.log(c('green', ` ✓ ${line}`));
|
|
363
|
+
if (issues.length) {
|
|
364
|
+
ok = false;
|
|
365
|
+
for (const line of issues) console.log(c('yellow', ` • ${line}`));
|
|
366
|
+
console.log(c('cyan', ` -> Run "frontmcp init" to apply the required settings.`));
|
|
367
|
+
}
|
|
297
368
|
} else {
|
|
298
369
|
ok = false;
|
|
299
370
|
console.log(`❌ tsconfig.json not found — run ${c('cyan', 'frontmcp init')}`);
|
|
@@ -315,6 +386,171 @@ async function runDoctor(): Promise<void> {
|
|
|
315
386
|
}
|
|
316
387
|
}
|
|
317
388
|
|
|
389
|
+
/* ------------------------------- Inspector -------------------------------- */
|
|
390
|
+
|
|
391
|
+
async function runInspector(): Promise<void> {
|
|
392
|
+
console.log(`${c('cyan', '[inspector]')} launching MCP Inspector...`);
|
|
393
|
+
await runCmd('npx', ['-y', '@modelcontextprotocol/inspector']);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* --------------------------------- Create --------------------------------- */
|
|
397
|
+
|
|
398
|
+
function pkgNameFromCwd(cwd: string) {
|
|
399
|
+
return path.basename(cwd).replace(/[^a-zA-Z0-9._-]/g, '-').toLowerCase() || 'frontmcp-app';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function upsertPackageJson(cwd: string) {
|
|
403
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
404
|
+
const existing = await readJSON<Record<string, any>>(pkgPath);
|
|
405
|
+
|
|
406
|
+
const base = {
|
|
407
|
+
name: pkgNameFromCwd(cwd),
|
|
408
|
+
version: '0.1.0',
|
|
409
|
+
private: true,
|
|
410
|
+
type: 'module',
|
|
411
|
+
main: 'src/main.ts',
|
|
412
|
+
scripts: {
|
|
413
|
+
dev: 'frontmcp dev',
|
|
414
|
+
build: 'frontmcp build',
|
|
415
|
+
inspect: 'frontmcp inspector',
|
|
416
|
+
doctor: 'frontmcp doctor',
|
|
417
|
+
},
|
|
418
|
+
engines: {
|
|
419
|
+
node: '>=22',
|
|
420
|
+
npm: '>=10',
|
|
421
|
+
},
|
|
422
|
+
dependencies: {
|
|
423
|
+
'@frontmcp/sdk': 'latest',
|
|
424
|
+
zod: 'latest',
|
|
425
|
+
},
|
|
426
|
+
devDependencies: {
|
|
427
|
+
typescript: 'latest',
|
|
428
|
+
tsx: 'latest',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (!existing) {
|
|
433
|
+
await writeJSON(pkgPath, base);
|
|
434
|
+
console.log(c('green', '✅ Created package.json (with scripts: dev, build, inspect, doctor)'));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Merge, preserving user fields; ensure our requirements exist
|
|
439
|
+
const merged = {...base, ...existing};
|
|
440
|
+
|
|
441
|
+
merged.main = existing.main || base.main;
|
|
442
|
+
merged.type = existing.type || base.type;
|
|
443
|
+
|
|
444
|
+
merged.scripts = {
|
|
445
|
+
...base.scripts,
|
|
446
|
+
...(existing.scripts || {}),
|
|
447
|
+
dev: (existing.scripts?.dev ?? base.scripts.dev),
|
|
448
|
+
build: (existing.scripts?.build ?? base.scripts.build),
|
|
449
|
+
inspect: (existing.scripts?.inspect ?? base.scripts.inspect),
|
|
450
|
+
doctor: (existing.scripts?.doctor ?? base.scripts.doctor),
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
merged.engines = {
|
|
454
|
+
...(existing.engines || {}),
|
|
455
|
+
node: existing.engines?.node || base.engines.node,
|
|
456
|
+
npm: existing.engines?.npm || base.engines.npm,
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
merged.dependencies = {
|
|
460
|
+
...existing.dependencies,
|
|
461
|
+
'@frontmcp/sdk': existing.dependencies?.['@frontmcp/sdk'] || base.dependencies['@frontmcp/sdk'],
|
|
462
|
+
zod: existing.dependencies?.zod || base.dependencies.zod,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
merged.devDependencies = {
|
|
466
|
+
...existing.devDependencies,
|
|
467
|
+
typescript: existing.devDependencies?.typescript || base.devDependencies.typescript,
|
|
468
|
+
tsx: existing.devDependencies?.tsx || base.devDependencies.tsx,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
await writeJSON(pkgPath, merged);
|
|
472
|
+
console.log(c('green', '✅ Updated package.json (ensured scripts, engines, deps)'));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function scaffoldFileIfMissing(p: string, content: string) {
|
|
476
|
+
if (await fileExists(p)) {
|
|
477
|
+
console.log(c('gray', `skip: ${path.relative(process.cwd(), p)} already exists`));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
await ensureDir(path.dirname(p));
|
|
481
|
+
await fsp.writeFile(p, content.replace(/^\n/, ''), 'utf8');
|
|
482
|
+
console.log(c('green', `✓ created ${path.relative(process.cwd(), p)}`));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const TEMPLATE_MAIN_TS = `
|
|
486
|
+
import 'reflect-metadata';
|
|
487
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
488
|
+
import { CalcApp } from './calc.app';
|
|
489
|
+
|
|
490
|
+
@FrontMcp({
|
|
491
|
+
info: { name: 'Demo 🚀', version: '0.1.0' },
|
|
492
|
+
apps: [CalcApp],
|
|
493
|
+
auth: {
|
|
494
|
+
type: 'remote',
|
|
495
|
+
name: 'my-remote-auth',
|
|
496
|
+
baseUrl: 'https://idp.example.com',
|
|
497
|
+
},
|
|
498
|
+
})
|
|
499
|
+
export default class Server {}
|
|
500
|
+
`;
|
|
501
|
+
|
|
502
|
+
const TEMPLATE_CALC_APP_TS = `
|
|
503
|
+
import { App } from '@frontmcp/sdk';
|
|
504
|
+
import AddTool from './tools/add.tool';
|
|
505
|
+
|
|
506
|
+
@App({
|
|
507
|
+
id: 'calc',
|
|
508
|
+
name: 'Calculator',
|
|
509
|
+
tools: [AddTool],
|
|
510
|
+
})
|
|
511
|
+
export class CalcApp {}
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
const TEMPLATE_ADD_TOOL_TS = `
|
|
515
|
+
import { tool } from '@frontmcp/sdk';
|
|
516
|
+
import { z } from 'zod';
|
|
517
|
+
|
|
518
|
+
const AddTool = tool({
|
|
519
|
+
name: 'add',
|
|
520
|
+
description: 'Add two numbers',
|
|
521
|
+
inputSchema: z.object({ a: z.number(), b: z.number() }),
|
|
522
|
+
outputSchema: z.object({ result: z.number() }),
|
|
523
|
+
})((input, _ctx) => {
|
|
524
|
+
return { result: input.a + input.b };
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
export default AddTool;
|
|
528
|
+
`;
|
|
529
|
+
|
|
530
|
+
async function runCreate(): Promise<void> {
|
|
531
|
+
const cwd = process.cwd();
|
|
532
|
+
|
|
533
|
+
console.log(c('cyan', '[create]'), 'Scaffolding FrontMCP project in', c('bold', cwd));
|
|
534
|
+
|
|
535
|
+
// 1) tsconfig
|
|
536
|
+
await runInit();
|
|
537
|
+
|
|
538
|
+
// 2) package.json
|
|
539
|
+
await upsertPackageJson(cwd);
|
|
540
|
+
|
|
541
|
+
// 3) files
|
|
542
|
+
await scaffoldFileIfMissing(path.join(cwd, 'src', 'main.ts'), TEMPLATE_MAIN_TS);
|
|
543
|
+
await scaffoldFileIfMissing(path.join(cwd, 'src', 'calc.app.ts'), TEMPLATE_CALC_APP_TS);
|
|
544
|
+
await scaffoldFileIfMissing(path.join(cwd, 'src', 'tools', 'add.tool.ts'), TEMPLATE_ADD_TOOL_TS);
|
|
545
|
+
|
|
546
|
+
// 4) final tips
|
|
547
|
+
console.log('\nNext steps:');
|
|
548
|
+
console.log(' 1) npm install');
|
|
549
|
+
console.log(' 2) npm run dev ', c('gray', '# starts tsx watcher via frontmcp dev'));
|
|
550
|
+
console.log(' 3) npm run inspect', c('gray', '# launch MCP Inspector'));
|
|
551
|
+
console.log(' 4) npm run build ', c('gray', '# compile with tsc via frontmcp build'));
|
|
552
|
+
}
|
|
553
|
+
|
|
318
554
|
/* --------------------------------- Main ----------------------------------- */
|
|
319
555
|
|
|
320
556
|
async function main(): Promise<void> {
|
|
@@ -342,6 +578,12 @@ async function main(): Promise<void> {
|
|
|
342
578
|
case 'doctor':
|
|
343
579
|
await runDoctor();
|
|
344
580
|
break;
|
|
581
|
+
case 'inspector':
|
|
582
|
+
await runInspector();
|
|
583
|
+
break;
|
|
584
|
+
case 'create':
|
|
585
|
+
await runCreate();
|
|
586
|
+
break;
|
|
345
587
|
case 'help':
|
|
346
588
|
showHelp();
|
|
347
589
|
break;
|