UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

121 lines (107 loc) 4 kB
#!/usr/bin/env node 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();