@tac0de/project-bootstrap-mcp 1.0.2 → 1.1.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.
- package/bin/project-bootstrap-mcp.js +1 -1
- package/dist/app.d.ts +11 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +65 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +32 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +25 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/validators.d.ts +6 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +36 -0
- package/package.json +13 -4
- package/src/app.ts +73 -0
- package/src/index.ts +5 -0
- package/src/server.ts +44 -0
- package/src/store.ts +25 -0
- package/src/types.ts +18 -0
- package/src/{validators.js → validators.ts} +16 -7
- package/test/{store.test.js → store.test.ts} +5 -7
- package/src/app.js +0 -71
- package/src/index.js +0 -9
- package/src/store.js +0 -27
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import BootstrapStore from './store';
|
|
3
|
+
interface CreateAppOptions {
|
|
4
|
+
store?: BootstrapStore;
|
|
5
|
+
}
|
|
6
|
+
export default function createApp(options?: CreateAppOptions): {
|
|
7
|
+
app: express.Express;
|
|
8
|
+
store: BootstrapStore;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAqD,MAAM,SAAS,CAAC;AAG5E,OAAO,cAAc,MAAM,SAAS,CAAC;AAGrC,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EA8D/D"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = createApp;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
9
|
+
const validators_1 = require("./validators");
|
|
10
|
+
const store_1 = __importDefault(require("./store"));
|
|
11
|
+
function createApp(options = {}) {
|
|
12
|
+
const store = options.store ?? new store_1.default();
|
|
13
|
+
const app = (0, express_1.default)();
|
|
14
|
+
app.use(express_1.default.json());
|
|
15
|
+
app.get('/api/status', (_req, res) => {
|
|
16
|
+
res.json({
|
|
17
|
+
service: package_json_1.default.name,
|
|
18
|
+
version: package_json_1.default.version,
|
|
19
|
+
uptime: process.uptime(),
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
app.get('/api/bootstraps', (_req, res) => {
|
|
24
|
+
res.json({ items: store.list() });
|
|
25
|
+
});
|
|
26
|
+
app.post('/api/bootstraps', (req, res) => {
|
|
27
|
+
const validation = (0, validators_1.validateBootstrapPayload)(req.body);
|
|
28
|
+
if (!validation.valid) {
|
|
29
|
+
return res.status(400).json({ errors: validation.errors });
|
|
30
|
+
}
|
|
31
|
+
const payload = {
|
|
32
|
+
name: req.body.name,
|
|
33
|
+
description: req.body.description,
|
|
34
|
+
primaryAction: req.body.primaryAction,
|
|
35
|
+
targetPlatforms: Array.isArray(req.body.targetPlatforms)
|
|
36
|
+
? req.body.targetPlatforms.map(String)
|
|
37
|
+
: [],
|
|
38
|
+
successCriteria: Array.isArray(req.body.successCriteria)
|
|
39
|
+
? req.body.successCriteria.map(String)
|
|
40
|
+
: [],
|
|
41
|
+
steps: Array.isArray(req.body.steps)
|
|
42
|
+
? req.body.steps
|
|
43
|
+
: [],
|
|
44
|
+
};
|
|
45
|
+
const saved = store.add(payload);
|
|
46
|
+
res.status(201).json(saved);
|
|
47
|
+
});
|
|
48
|
+
app.get('/api/bootstraps/:id', (req, res) => {
|
|
49
|
+
const record = store.find(req.params.id);
|
|
50
|
+
if (!record) {
|
|
51
|
+
return res.status(404).json({ error: 'bootstrap not found' });
|
|
52
|
+
}
|
|
53
|
+
res.json(record);
|
|
54
|
+
});
|
|
55
|
+
app.use((_req, res) => {
|
|
56
|
+
res.status(404).json({ error: 'not found' });
|
|
57
|
+
});
|
|
58
|
+
app.use((err, _req, res, next) => {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error('internal error', err);
|
|
61
|
+
res.status(500).json({ error: 'internal server error' });
|
|
62
|
+
next(err);
|
|
63
|
+
});
|
|
64
|
+
return { app, store };
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,SAAS,CAAC;AACrC,OAAO,SAAS,MAAM,OAAO,CAAC;AAC9B,OAAO,WAAW,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startServer = exports.createApp = exports.BootstrapStore = void 0;
|
|
7
|
+
const store_1 = __importDefault(require("./store"));
|
|
8
|
+
exports.BootstrapStore = store_1.default;
|
|
9
|
+
const app_1 = __importDefault(require("./app"));
|
|
10
|
+
exports.createApp = app_1.default;
|
|
11
|
+
const server_1 = __importDefault(require("./server"));
|
|
12
|
+
exports.startServer = server_1.default;
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import type { Server } from 'http';
|
|
3
|
+
export interface StartServerOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
onStart?: (message: string, port: number) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface StartServerResult {
|
|
8
|
+
app: Express;
|
|
9
|
+
server: Server;
|
|
10
|
+
}
|
|
11
|
+
export default function startServer({ port, onStart, }?: StartServerOptions): StartServerResult;
|
|
12
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,IAAI,EACJ,OAAO,GACR,GAAE,kBAAuB,GAAG,iBAAiB,CA0B7C"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = startServer;
|
|
7
|
+
const app_1 = __importDefault(require("./app"));
|
|
8
|
+
function startServer({ port, onStart, } = {}) {
|
|
9
|
+
const defaultPort = Number(process.env.PORT) || 4000;
|
|
10
|
+
const resolvedPort = port ?? defaultPort;
|
|
11
|
+
const { app } = (0, app_1.default)();
|
|
12
|
+
const server = app.listen(resolvedPort, () => {
|
|
13
|
+
const message = `Project Bootstrap MCP listening on port ${resolvedPort}`;
|
|
14
|
+
if (typeof onStart === 'function') {
|
|
15
|
+
onStart(message, resolvedPort);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.log(message);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const graceful = () => {
|
|
23
|
+
server.close(() => {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.log('Project Bootstrap MCP shutting down');
|
|
26
|
+
process.exit(0);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
process.on('SIGINT', graceful);
|
|
30
|
+
process.on('SIGTERM', graceful);
|
|
31
|
+
return { app, server };
|
|
32
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BootstrapPayload, BootstrapRecord } from './types';
|
|
2
|
+
export default class BootstrapStore {
|
|
3
|
+
private readonly items;
|
|
4
|
+
private counter;
|
|
5
|
+
list(): BootstrapRecord[];
|
|
6
|
+
add(payload: BootstrapPayload): BootstrapRecord;
|
|
7
|
+
find(id: string): BootstrapRecord | null;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEjE,MAAM,CAAC,OAAO,OAAO,cAAc;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsC;IAC5D,OAAO,CAAC,OAAO,CAAK;IAEb,IAAI,IAAI,eAAe,EAAE;IAIzB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe;IAW/C,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;CAGhD"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class BootstrapStore {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.items = new Map();
|
|
6
|
+
this.counter = 1;
|
|
7
|
+
}
|
|
8
|
+
list() {
|
|
9
|
+
return Array.from(this.items.values());
|
|
10
|
+
}
|
|
11
|
+
add(payload) {
|
|
12
|
+
const id = String(this.counter++);
|
|
13
|
+
const record = {
|
|
14
|
+
id,
|
|
15
|
+
createdAt: new Date().toISOString(),
|
|
16
|
+
...payload,
|
|
17
|
+
};
|
|
18
|
+
this.items.set(id, record);
|
|
19
|
+
return record;
|
|
20
|
+
}
|
|
21
|
+
find(id) {
|
|
22
|
+
return this.items.get(id) ?? null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.default = BootstrapStore;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface BootstrapStep {
|
|
2
|
+
title: string;
|
|
3
|
+
detail: string;
|
|
4
|
+
}
|
|
5
|
+
export interface BootstrapPayload {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
primaryAction: string;
|
|
9
|
+
targetPlatforms?: string[];
|
|
10
|
+
successCriteria?: string[];
|
|
11
|
+
steps: BootstrapStep[];
|
|
12
|
+
}
|
|
13
|
+
export interface BootstrapRecord extends BootstrapPayload {
|
|
14
|
+
id: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAmC3E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateBootstrapPayload = validateBootstrapPayload;
|
|
4
|
+
function validateBootstrapPayload(payload) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
if (!payload || typeof payload !== 'object') {
|
|
7
|
+
errors.push('payload must be an object');
|
|
8
|
+
return { valid: false, errors };
|
|
9
|
+
}
|
|
10
|
+
const typedPayload = payload;
|
|
11
|
+
const { name, description, primaryAction, steps } = typedPayload;
|
|
12
|
+
if (!name || typeof name !== 'string') {
|
|
13
|
+
errors.push('name is required and must be a string');
|
|
14
|
+
}
|
|
15
|
+
if (!description || typeof description !== 'string') {
|
|
16
|
+
errors.push('description is required and must be a string');
|
|
17
|
+
}
|
|
18
|
+
if (!primaryAction || typeof primaryAction !== 'string') {
|
|
19
|
+
errors.push('primaryAction is required and must be a string');
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
22
|
+
errors.push('steps must be a non-empty array');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
steps.forEach((step, index) => {
|
|
26
|
+
const currentStep = step;
|
|
27
|
+
if (!currentStep || typeof currentStep.title !== 'string') {
|
|
28
|
+
errors.push(`steps[${index}].title is required`);
|
|
29
|
+
}
|
|
30
|
+
if (!currentStep || typeof currentStep.detail !== 'string') {
|
|
31
|
+
errors.push(`steps[${index}].detail is required`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return { valid: errors.length === 0, errors };
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tac0de/project-bootstrap-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Project Bootstrap MCP API tooling for launching MCP bootstrap definitions and documentation.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"project-bootstrap-mcp": "bin/project-bootstrap-mcp.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "npm run build && node ./bin/project-bootstrap-mcp.js",
|
|
12
|
+
"test": "node --test -r ts-node/register test/store.test.ts"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
14
15
|
"mcp",
|
|
@@ -28,17 +29,25 @@
|
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"bin",
|
|
32
|
+
"dist",
|
|
31
33
|
"src",
|
|
32
34
|
"test",
|
|
33
35
|
"LICENSE",
|
|
34
36
|
"README.md",
|
|
35
37
|
"TRD.md"
|
|
36
38
|
],
|
|
39
|
+
"types": "dist/index.d.ts",
|
|
37
40
|
"repository": {
|
|
38
41
|
"type": "git",
|
|
39
42
|
"url": "https://github.com/tac0de/project-bootstrap-mcp"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
42
45
|
"express": "^4.19.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/express": "^5.0.6",
|
|
49
|
+
"@types/node": "^25.0.3",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
43
52
|
}
|
|
44
53
|
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import express, { Express, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import pkg from '../package.json';
|
|
3
|
+
import { validateBootstrapPayload } from './validators';
|
|
4
|
+
import BootstrapStore from './store';
|
|
5
|
+
import type { BootstrapPayload } from './types';
|
|
6
|
+
|
|
7
|
+
interface CreateAppOptions {
|
|
8
|
+
store?: BootstrapStore;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function createApp(options: CreateAppOptions = {}) {
|
|
12
|
+
const store = options.store ?? new BootstrapStore();
|
|
13
|
+
const app: Express = express();
|
|
14
|
+
|
|
15
|
+
app.use(express.json());
|
|
16
|
+
|
|
17
|
+
app.get('/api/status', (_req: Request, res: Response) => {
|
|
18
|
+
res.json({
|
|
19
|
+
service: pkg.name,
|
|
20
|
+
version: pkg.version,
|
|
21
|
+
uptime: process.uptime(),
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
app.get('/api/bootstraps', (_req: Request, res: Response) => {
|
|
27
|
+
res.json({ items: store.list() });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
app.post('/api/bootstraps', (req: Request, res: Response) => {
|
|
31
|
+
const validation = validateBootstrapPayload(req.body);
|
|
32
|
+
if (!validation.valid) {
|
|
33
|
+
return res.status(400).json({ errors: validation.errors });
|
|
34
|
+
}
|
|
35
|
+
const payload: BootstrapPayload = {
|
|
36
|
+
name: req.body.name,
|
|
37
|
+
description: req.body.description,
|
|
38
|
+
primaryAction: req.body.primaryAction,
|
|
39
|
+
targetPlatforms: Array.isArray(req.body.targetPlatforms)
|
|
40
|
+
? req.body.targetPlatforms.map(String)
|
|
41
|
+
: [],
|
|
42
|
+
successCriteria: Array.isArray(req.body.successCriteria)
|
|
43
|
+
? req.body.successCriteria.map(String)
|
|
44
|
+
: [],
|
|
45
|
+
steps: Array.isArray(req.body.steps)
|
|
46
|
+
? (req.body.steps as BootstrapPayload['steps'])
|
|
47
|
+
: [],
|
|
48
|
+
};
|
|
49
|
+
const saved = store.add(payload);
|
|
50
|
+
res.status(201).json(saved);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
app.get('/api/bootstraps/:id', (req: Request, res: Response) => {
|
|
54
|
+
const record = store.find(req.params.id);
|
|
55
|
+
if (!record) {
|
|
56
|
+
return res.status(404).json({ error: 'bootstrap not found' });
|
|
57
|
+
}
|
|
58
|
+
res.json(record);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
app.use((_req: Request, res: Response) => {
|
|
62
|
+
res.status(404).json({ error: 'not found' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.use((err: Error, _req: Request, res: Response, next: NextFunction) => {
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.error('internal error', err);
|
|
68
|
+
res.status(500).json({ error: 'internal server error' });
|
|
69
|
+
next(err);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { app, store };
|
|
73
|
+
}
|
package/src/index.ts
ADDED
package/src/server.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import type { Server } from 'http';
|
|
3
|
+
import createApp from './app';
|
|
4
|
+
|
|
5
|
+
export interface StartServerOptions {
|
|
6
|
+
port?: number;
|
|
7
|
+
onStart?: (message: string, port: number) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface StartServerResult {
|
|
11
|
+
app: Express;
|
|
12
|
+
server: Server;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function startServer({
|
|
16
|
+
port,
|
|
17
|
+
onStart,
|
|
18
|
+
}: StartServerOptions = {}): StartServerResult {
|
|
19
|
+
const defaultPort = Number(process.env.PORT) || 4000;
|
|
20
|
+
const resolvedPort = port ?? defaultPort;
|
|
21
|
+
const { app } = createApp();
|
|
22
|
+
const server = app.listen(resolvedPort, () => {
|
|
23
|
+
const message = `Project Bootstrap MCP listening on port ${resolvedPort}`;
|
|
24
|
+
if (typeof onStart === 'function') {
|
|
25
|
+
onStart(message, resolvedPort);
|
|
26
|
+
} else {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log(message);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const graceful = () => {
|
|
33
|
+
server.close(() => {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log('Project Bootstrap MCP shutting down');
|
|
36
|
+
process.exit(0);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
process.on('SIGINT', graceful);
|
|
41
|
+
process.on('SIGTERM', graceful);
|
|
42
|
+
|
|
43
|
+
return { app, server };
|
|
44
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BootstrapPayload, BootstrapRecord } from './types';
|
|
2
|
+
|
|
3
|
+
export default class BootstrapStore {
|
|
4
|
+
private readonly items = new Map<string, BootstrapRecord>();
|
|
5
|
+
private counter = 1;
|
|
6
|
+
|
|
7
|
+
public list(): BootstrapRecord[] {
|
|
8
|
+
return Array.from(this.items.values());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public add(payload: BootstrapPayload): BootstrapRecord {
|
|
12
|
+
const id = String(this.counter++);
|
|
13
|
+
const record: BootstrapRecord = {
|
|
14
|
+
id,
|
|
15
|
+
createdAt: new Date().toISOString(),
|
|
16
|
+
...payload,
|
|
17
|
+
};
|
|
18
|
+
this.items.set(id, record);
|
|
19
|
+
return record;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public find(id: string): BootstrapRecord | null {
|
|
23
|
+
return this.items.get(id) ?? null;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BootstrapStep {
|
|
2
|
+
title: string;
|
|
3
|
+
detail: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface BootstrapPayload {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
primaryAction: string;
|
|
10
|
+
targetPlatforms?: string[];
|
|
11
|
+
successCriteria?: string[];
|
|
12
|
+
steps: BootstrapStep[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BootstrapRecord extends BootstrapPayload {
|
|
16
|
+
id: string;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { BootstrapPayload, BootstrapStep } from './types';
|
|
2
|
+
|
|
3
|
+
export interface ValidationResult {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
errors: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function validateBootstrapPayload(payload: unknown): ValidationResult {
|
|
9
|
+
const errors: string[] = [];
|
|
3
10
|
if (!payload || typeof payload !== 'object') {
|
|
4
11
|
errors.push('payload must be an object');
|
|
5
12
|
return { valid: false, errors };
|
|
6
13
|
}
|
|
7
14
|
|
|
8
|
-
const
|
|
15
|
+
const typedPayload = payload as BootstrapPayload;
|
|
16
|
+
const { name, description, primaryAction, steps } = typedPayload;
|
|
17
|
+
|
|
9
18
|
if (!name || typeof name !== 'string') {
|
|
10
19
|
errors.push('name is required and must be a string');
|
|
11
20
|
}
|
|
@@ -15,14 +24,16 @@ function validateBootstrapPayload(payload) {
|
|
|
15
24
|
if (!primaryAction || typeof primaryAction !== 'string') {
|
|
16
25
|
errors.push('primaryAction is required and must be a string');
|
|
17
26
|
}
|
|
27
|
+
|
|
18
28
|
if (!Array.isArray(steps) || steps.length === 0) {
|
|
19
29
|
errors.push('steps must be a non-empty array');
|
|
20
30
|
} else {
|
|
21
31
|
steps.forEach((step, index) => {
|
|
22
|
-
|
|
32
|
+
const currentStep = step as BootstrapStep | undefined;
|
|
33
|
+
if (!currentStep || typeof currentStep.title !== 'string') {
|
|
23
34
|
errors.push(`steps[${index}].title is required`);
|
|
24
35
|
}
|
|
25
|
-
if (!
|
|
36
|
+
if (!currentStep || typeof currentStep.detail !== 'string') {
|
|
26
37
|
errors.push(`steps[${index}].detail is required`);
|
|
27
38
|
}
|
|
28
39
|
});
|
|
@@ -30,5 +41,3 @@ function validateBootstrapPayload(payload) {
|
|
|
30
41
|
|
|
31
42
|
return { valid: errors.length === 0, errors };
|
|
32
43
|
}
|
|
33
|
-
|
|
34
|
-
module.exports = { validateBootstrapPayload };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import BootstrapStore from '../src/store';
|
|
4
|
+
import { validateBootstrapPayload } from '../src/validators';
|
|
5
5
|
|
|
6
6
|
test('BootstrapStore should add and list records', () => {
|
|
7
7
|
const store = new BootstrapStore();
|
|
@@ -9,9 +9,7 @@ test('BootstrapStore should add and list records', () => {
|
|
|
9
9
|
name: 'bootstrap-a',
|
|
10
10
|
description: 'test',
|
|
11
11
|
primaryAction: 'deploy',
|
|
12
|
-
steps: [
|
|
13
|
-
{ title: 'step1', detail: 'run' },
|
|
14
|
-
],
|
|
12
|
+
steps: [{ title: 'step1', detail: 'run' }],
|
|
15
13
|
};
|
|
16
14
|
|
|
17
15
|
const record = store.add(payload);
|
package/src/app.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { validateBootstrapPayload } = require('./validators');
|
|
4
|
-
const BootstrapStore = require('./store');
|
|
5
|
-
|
|
6
|
-
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
7
|
-
|
|
8
|
-
function createApp(options = {}) {
|
|
9
|
-
const store = options.store || new BootstrapStore();
|
|
10
|
-
const app = express();
|
|
11
|
-
|
|
12
|
-
app.use(express.json());
|
|
13
|
-
|
|
14
|
-
app.get('/api/status', (req, res) => {
|
|
15
|
-
res.json({
|
|
16
|
-
service: pkg.name,
|
|
17
|
-
version: pkg.version,
|
|
18
|
-
uptime: process.uptime(),
|
|
19
|
-
timestamp: new Date().toISOString(),
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
app.get('/api/bootstraps', (req, res) => {
|
|
24
|
-
res.json({ items: store.list() });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
app.post('/api/bootstraps', (req, res) => {
|
|
28
|
-
const validation = validateBootstrapPayload(req.body);
|
|
29
|
-
if (!validation.valid) {
|
|
30
|
-
return res.status(400).json({ errors: validation.errors });
|
|
31
|
-
}
|
|
32
|
-
const payload = {
|
|
33
|
-
name: req.body.name,
|
|
34
|
-
description: req.body.description,
|
|
35
|
-
primaryAction: req.body.primaryAction,
|
|
36
|
-
targetPlatforms: Array.isArray(req.body.targetPlatforms)
|
|
37
|
-
? req.body.targetPlatforms
|
|
38
|
-
: [],
|
|
39
|
-
successCriteria: Array.isArray(req.body.successCriteria)
|
|
40
|
-
? req.body.successCriteria
|
|
41
|
-
: [],
|
|
42
|
-
steps: req.body.steps,
|
|
43
|
-
};
|
|
44
|
-
const saved = store.add(payload);
|
|
45
|
-
res.status(201).json(saved);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
app.get('/api/bootstraps/:id', (req, res) => {
|
|
49
|
-
const record = store.find(req.params.id);
|
|
50
|
-
if (!record) {
|
|
51
|
-
return res.status(404).json({ error: 'bootstrap not found' });
|
|
52
|
-
}
|
|
53
|
-
res.json(record);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
app.use((req, res) => {
|
|
57
|
-
res.status(404).json({ error: 'not found' });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
app.use((err, req, res, next) => {
|
|
61
|
-
/* eslint-disable no-console */
|
|
62
|
-
console.error('internal error', err);
|
|
63
|
-
/* eslint-enable no-console */
|
|
64
|
-
res.status(500).json({ error: 'internal server error' });
|
|
65
|
-
next(err);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return { app, store };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = createApp;
|
package/src/index.js
DELETED
package/src/store.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
class BootstrapStore {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.items = new Map();
|
|
4
|
-
this.counter = 1;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
list() {
|
|
8
|
-
return Array.from(this.items.values());
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
add(payload) {
|
|
12
|
-
const id = String(this.counter++);
|
|
13
|
-
const record = {
|
|
14
|
-
id,
|
|
15
|
-
createdAt: new Date().toISOString(),
|
|
16
|
-
...payload,
|
|
17
|
-
};
|
|
18
|
-
this.items.set(id, record);
|
|
19
|
-
return record;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
find(id) {
|
|
23
|
-
return this.items.get(id) ?? null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
module.exports = BootstrapStore;
|