UNPKG

bc-symbols-mcp

Version:

MCP server for analyzing Business Central .app files and BC object structures

261 lines 9.47 kB
import { SymbolParser } from '../processors/symbol-parser.js'; export class BCResources { cache; symbolParser; constructor(cache) { this.cache = cache; this.symbolParser = new SymbolParser(); } /** * List all available resources */ async listResources() { const apps = this.cache.getValidCachedApps(); const resources = []; for (const app of apps) { // App manifest resource resources.push({ uri: `bc-app://${app.id}/manifest`, name: `${app.name} - Manifest`, description: `Manifest data for ${app.name} (${app.publisher})`, mimeType: 'application/json' }); // App symbols resource resources.push({ uri: `bc-app://${app.id}/symbols`, name: `${app.name} - Symbols`, description: `Symbol reference data for ${app.name} (${app.publisher})`, mimeType: 'application/json' }); // Object type resources const objectTypes = this.symbolParser.getAvailableObjectTypes(app.symbols); for (const objectType of objectTypes) { resources.push({ uri: `bc-app://${app.id}/objects/${objectType}`, name: `${app.name} - ${objectType} Objects`, description: `All ${objectType} objects in ${app.name}`, mimeType: 'application/json' }); } // App info resource resources.push({ uri: `bc-app://${app.id}/info`, name: `${app.name} - App Info`, description: `Basic information about ${app.name}`, mimeType: 'application/json' }); // Dependencies resource resources.push({ uri: `bc-app://${app.id}/dependencies`, name: `${app.name} - Dependencies`, description: `Dependency information for ${app.name}`, mimeType: 'application/json' }); } // Global resources resources.push({ uri: 'bc-apps://all', name: 'All Loaded Apps', description: 'List of all loaded BC apps', mimeType: 'application/json' }); resources.push({ uri: 'bc-apps://cache-stats', name: 'Cache Statistics', description: 'Current cache statistics and memory usage', mimeType: 'application/json' }); return resources; } /** * Read a specific resource */ async readResource(uri) { const url = new URL(uri); if (url.protocol === 'bc-app:') { return this.readAppResource(url); } else if (url.protocol === 'bc-apps:') { return this.readGlobalResource(url); } else { throw new Error(`Unsupported URI protocol: ${url.protocol}`); } } /** * Read app-specific resource */ async readAppResource(url) { const pathParts = url.pathname.split('/').filter(p => p); if (pathParts.length < 2) { throw new Error(`Invalid app resource URI: ${url.toString()}`); } const appId = pathParts[0]; const resourceType = pathParts[1]; const resourceSubType = pathParts[2]; const app = this.cache.findAppById(appId); if (!app) { throw new Error(`App not found: ${appId}`); } switch (resourceType) { case 'manifest': return JSON.stringify(app.manifest, null, 2); case 'symbols': return JSON.stringify(app.symbols, null, 2); case 'objects': if (!resourceSubType) { throw new Error('Object type not specified'); } return this.getObjectsByType(app, resourceSubType); case 'info': return JSON.stringify({ id: app.id, name: app.name, publisher: app.publisher, version: app.version, description: app.description, brief: app.brief, platform: app.platform, application: app.application, runtime: app.runtime, target: app.target, filePath: app.filePath, fileHash: app.fileHash, idRanges: app.idRanges }, null, 2); case 'dependencies': return JSON.stringify({ directDependencies: app.dependencies, dependencyTree: this.cache.getDependencyTree(app.id), reverseDependencies: this.cache.getReverseDependencyTree(app.id) }, null, 2); default: throw new Error(`Unknown resource type: ${resourceType}`); } } /** * Read global resource */ async readGlobalResource(url) { const resourceType = url.pathname.substring(1); // Remove leading slash switch (resourceType) { case 'all': { const apps = this.cache.getValidCachedApps(); return JSON.stringify(apps.map(app => ({ id: app.id, name: app.name, publisher: app.publisher, version: app.version, description: app.description, filePath: app.filePath, dependencyCount: app.dependencies.length, objectCounts: this.getObjectCounts(app) })), null, 2); } case 'cache-stats': return JSON.stringify(this.cache.getStats(), null, 2); default: throw new Error(`Unknown global resource: ${resourceType}`); } } /** * Get objects by type for an app */ getObjectsByType(app, objectType) { const objects = this.symbolParser.findObjectsByType(app.symbols, objectType); // Return simplified object information for better readability const simplifiedObjects = objects.map(obj => ({ id: obj.id, name: obj.name, referenceSourceFileName: obj.referenceSourceFileName, properties: obj.properties, fieldCount: obj.fields ? obj.fields.length : 0, methodCount: obj.methods ? obj.methods.length : 0, variableCount: obj.variables ? obj.variables.length : 0 })); return JSON.stringify(simplifiedObjects, null, 2); } /** * Get object counts for an app */ getObjectCounts(app) { const counts = {}; const objectTypes = this.symbolParser.getAvailableObjectTypes(app.symbols); for (const objectType of objectTypes) { const objects = this.symbolParser.findObjectsByType(app.symbols, objectType); counts[objectType] = objects.length; } return counts; } /** * Search resources by query */ async searchResources(query) { const allResources = await this.listResources(); const lowerQuery = query.toLowerCase(); return allResources.filter(resource => resource.name.toLowerCase().includes(lowerQuery) || resource.description?.toLowerCase().includes(lowerQuery) || resource.uri.toLowerCase().includes(lowerQuery)); } /** * Get resource URI patterns */ getResourcePatterns() { return [ 'bc-app://{appId}/manifest', 'bc-app://{appId}/symbols', 'bc-app://{appId}/objects/{objectType}', 'bc-app://{appId}/info', 'bc-app://{appId}/dependencies', 'bc-apps://all', 'bc-apps://cache-stats' ]; } /** * Validate resource URI */ validateResourceUri(uri) { try { const url = new URL(uri); if (url.protocol === 'bc-app:') { const pathParts = url.pathname.split('/').filter(p => p); return pathParts.length >= 2; } else if (url.protocol === 'bc-apps:') { const resourceType = url.pathname.substring(1); return ['all', 'cache-stats'].includes(resourceType); } return false; } catch { return false; } } /** * Get resource metadata */ async getResourceMetadata(uri) { const content = await this.readResource(uri); const url = new URL(uri); const metadata = { size: Buffer.byteLength(content, 'utf8'), lastModified: new Date() }; if (url.protocol === 'bc-app:') { const appId = url.pathname.split('/')[1]; const app = this.cache.findAppById(appId); if (app) { return { ...metadata, appInfo: { id: app.id, name: app.name, version: app.version } }; } } return metadata; } } //# sourceMappingURL=bc-resources.js.map