mcp-product-manager
Version:
MCP Orchestrator for task and project management with web interface
121 lines (107 loc) • 4 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Import TOOL_PERMISSIONS for route discovery
import { TOOL_PERMISSIONS } from '../api/middleware/rbac.js';
const outPath = path.join(__dirname, '..', 'docs', 'docs-manifest.json');
const overridesPath = path.join(__dirname, '..', 'docs', 'docs-manifest.overrides.json');
const base = {
version: '1.0',
baseUrls: {
development: 'http://localhost:1234'
},
endpoints: [],
categories: {
'getting-started': ['docs/getting-started/quickstart.md'],
guides: ['docs/guides/mcp-integration.md'],
'api-reference': [
'docs/api/tasks.md',
'docs/api/projects.md',
'docs/api/agents.md',
'docs/api/orchestrator.md',
'docs/api/mcp.md',
'docs/api/usage.md',
'docs/api/bundles.md',
'docs/api/database.md',
'docs/api/rbac.md',
'docs/api/health.md'
]
}
};
function guessDocForPath(p) {
const seg = p.split('/')[2] || '';
const map = {
health: 'docs/api/health.md',
tasks: 'docs/api/tasks.md',
projects: 'docs/api/projects.md',
bundles: 'docs/api/bundles.md',
orchestrator: 'docs/api/orchestrator.md',
agents: 'docs/api/agents.md',
mcp: 'docs/api/mcp.md',
usage: 'docs/api/usage.md',
database: 'docs/api/database.md',
rbac: 'docs/api/rbac.md'
};
return map[seg] ? [map[seg]] : [];
}
function guessTestsForPath(p) {
const seg = p.split('/')[2] || '';
const tests = [];
if (seg === 'mcp') tests.push('__tests__/toolRegistry.test.js', '__tests__/restIntegration.test.js');
if (seg === 'tasks') tests.push('__tests__/toolAdapter.test.js', '__tests__/rbacRoutes.test.js');
if (seg === 'agents' || seg === 'projects') tests.push('__tests__/rbacRoutes.test.js');
if (seg === 'health') tests.push('__tests__/restIntegration.test.js');
return tests;
}
function guessTags(method, p) {
const seg = p.split('/')[2] || '';
const tags = [seg].filter(Boolean);
if (p.includes('status') || p === '/api/health') tags.push('status');
if (method === 'GET') tags.push('read');
if (method !== 'GET') tags.push('write');
return tags;
}
function build() {
const endpoints = Object.keys(TOOL_PERMISSIONS)
.filter(k => k.startsWith('GET ') || k.startsWith('POST ') || k.startsWith('PUT ') || k.startsWith('PATCH ') || k.startsWith('DELETE '))
.map(key => {
const [method, p] = key.split(' ');
return {
method,
path: p,
docs: guessDocForPath(p),
tests: guessTestsForPath(p),
tags: guessTags(method, p)
};
})
// De-duplicate identical endpoints
.filter((e, i, arr) => arr.findIndex(x => x.method === e.method && x.path === e.path) === i)
.sort((a, b) => (a.path + a.method).localeCompare(b.path + b.method));
let manifest = { ...base, endpoints };
if (fs.existsSync(overridesPath)) {
try {
const overrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8'));
// Merge overrides shallowly for endpoints (match by method+path)
if (Array.isArray(overrides.endpoints)) {
for (const o of overrides.endpoints) {
const idx = manifest.endpoints.findIndex(e => e.method === o.method && e.path === o.path);
if (idx >= 0) manifest.endpoints[idx] = { ...manifest.endpoints[idx], ...o };
else manifest.endpoints.push(o);
}
}
// Merge categories
if (overrides.categories) {
manifest.categories = { ...manifest.categories, ...overrides.categories };
}
} catch (err) {
console.error('Failed to apply docs manifest overrides:', err.message);
}
}
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2));
console.log('✅ docs-manifest.json generated with', manifest.endpoints.length, 'endpoints');
}
build();