Skip to main content
Glama
index.ts16.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; // Import knowledge bases import { backstageOverview } from './knowledge/backstage-overview.js'; import { pluginDevelopment } from './knowledge/plugin-development.js'; import { apiReference } from './knowledge/api-reference.js'; import { communityResources } from './knowledge/community-resources.js'; import { examples } from './knowledge/examples.js'; class BackstageMCPServer { private server: Server; private knowledgeBase: any; constructor() { this.server = new Server( { name: 'backstage-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.knowledgeBase = { overview: backstageOverview, pluginDev: pluginDevelopment, api: apiReference, community: communityResources, examples: examples, }; this.setupToolHandlers(); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_backstage_overview', description: 'Get comprehensive overview of Backstage framework including core features, benefits, and architecture', inputSchema: { type: 'object', properties: { section: { type: 'string', description: 'Specific section to retrieve (optional)', enum: ['whatIsBackstage', 'coreFeatures', 'benefits', 'architecture'] } } } }, { name: 'get_plugin_development_guide', description: 'Get detailed guide for developing Backstage plugins including setup, structure, and best practices', inputSchema: { type: 'object', properties: { topic: { type: 'string', description: 'Specific topic to retrieve (optional)', enum: ['overview', 'gettingStarted', 'pluginStructure', 'commonPatterns', 'apis', 'testing', 'deployment', 'bestPractices'] } } } }, { name: 'get_api_reference', description: 'Get Backstage API reference including REST endpoints, GraphQL, and client libraries', inputSchema: { type: 'object', properties: { api: { type: 'string', description: 'Specific API to retrieve (optional)', enum: ['catalogApi', 'scaffolderApi', 'techDocsApi', 'authApi', 'searchApi', 'proxyApi', 'graphqlApi'] } } } }, { name: 'get_community_resources', description: 'Get community resources, support channels, and common questions about Backstage', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Specific category to retrieve (optional)', enum: ['officialChannels', 'communityPlugins', 'commonQuestions', 'learningResources', 'adoptionStories', 'contributing'] } } } }, { name: 'get_backstage_examples', description: 'Get code examples and samples for common Backstage development scenarios', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of example to retrieve (optional)', enum: ['pluginExamples', 'catalogExamples', 'templateExamples', 'configExamples'] }, specific: { type: 'string', description: 'Specific example within the type (optional)' } } } }, { name: 'search_backstage_knowledge', description: 'Search across all Backstage knowledge for specific topics or keywords', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for finding relevant information' } }, required: ['query'] } }, { name: 'get_plugin_scaffold_template', description: 'Generate a plugin scaffold template with specified configuration', inputSchema: { type: 'object', properties: { pluginType: { type: 'string', description: 'Type of plugin to scaffold', enum: ['frontend', 'backend', 'fullstack', 'common'] }, pluginName: { type: 'string', description: 'Name of the plugin' }, features: { type: 'array', items: { type: 'string', enum: ['routing', 'api-client', 'entity-provider', 'scaffolder-action', 'search-collator'] }, description: 'Features to include in the plugin' } }, required: ['pluginType', 'pluginName'] } } ] as Tool[], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_backstage_overview': return this.getBackstageOverview(args?.section as string); case 'get_plugin_development_guide': return this.getPluginDevelopmentGuide(args?.topic as string); case 'get_api_reference': return this.getApiReference(args?.api as string); case 'get_community_resources': return this.getCommunityResources(args?.category as string); case 'get_backstage_examples': return this.getBackstageExamples(args?.type as string, args?.specific as string); case 'search_backstage_knowledge': return this.searchBackstageKnowledge(args?.query as string); case 'get_plugin_scaffold_template': return this.getPluginScaffoldTemplate(args?.pluginType as string, args?.pluginName as string, args?.features as string[]); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } }); } private getBackstageOverview(section?: string) { const content = section ? this.knowledgeBase.overview.content[section] : this.knowledgeBase.overview.content; return { content: [ { type: 'text', text: JSON.stringify(content, null, 2), }, ], }; } private getPluginDevelopmentGuide(topic?: string) { const content = topic ? this.knowledgeBase.pluginDev.content[topic] : this.knowledgeBase.pluginDev.content; return { content: [ { type: 'text', text: JSON.stringify(content, null, 2), }, ], }; } private getApiReference(api?: string) { const content = api ? this.knowledgeBase.api.content[api] : this.knowledgeBase.api.content; return { content: [ { type: 'text', text: JSON.stringify(content, null, 2), }, ], }; } private getCommunityResources(category?: string) { const content = category ? this.knowledgeBase.community.content[category] : this.knowledgeBase.community.content; return { content: [ { type: 'text', text: JSON.stringify(content, null, 2), }, ], }; } private getBackstageExamples(type?: string, specific?: string) { let content = this.knowledgeBase.examples.content; if (type) { content = content[type]; if (specific && content[specific]) { content = content[specific]; } } return { content: [ { type: 'text', text: JSON.stringify(content, null, 2), }, ], }; } private searchBackstageKnowledge(query: string) { const results: any[] = []; const searchTerm = query.toLowerCase(); // Search through all knowledge bases Object.entries(this.knowledgeBase).forEach(([key, knowledge]) => { const knowledgeStr = JSON.stringify(knowledge).toLowerCase(); if (knowledgeStr.includes(searchTerm)) { results.push({ source: key, title: (knowledge as any).title, relevantContent: this.extractRelevantContent(knowledge, searchTerm) }); } }); return { content: [ { type: 'text', text: JSON.stringify({ query, results, totalResults: results.length }, null, 2), }, ], }; } private extractRelevantContent(knowledge: any, searchTerm: string): any { // Simple relevance extraction - in a real implementation, this could be more sophisticated const content = JSON.stringify(knowledge.content); const index = content.toLowerCase().indexOf(searchTerm); if (index !== -1) { const start = Math.max(0, index - 100); const end = Math.min(content.length, index + 100); return content.substring(start, end); } return (knowledge as any).description; } private getPluginScaffoldTemplate(pluginType: string, pluginName: string, features: string[] = []) { const template = { pluginName, pluginType, features, files: this.generatePluginFiles(pluginType, pluginName, features), commands: this.generatePluginCommands(pluginType, pluginName), dependencies: this.generatePluginDependencies(pluginType, features) }; return { content: [ { type: 'text', text: JSON.stringify(template, null, 2), }, ], }; } private generatePluginFiles(pluginType: string, pluginName: string, features: string[]) { const files: any = {}; // Base files files[`plugins/${pluginName}/package.json`] = this.generatePackageJson(pluginName, pluginType); files[`plugins/${pluginName}/src/index.ts`] = this.generateIndexFile(pluginType); if (pluginType === 'frontend' || pluginType === 'fullstack') { files[`plugins/${pluginName}/src/plugin.ts`] = this.generateFrontendPlugin(pluginName); files[`plugins/${pluginName}/src/routes.ts`] = this.generateRoutes(); files[`plugins/${pluginName}/src/components/ExampleComponent.tsx`] = this.generateExampleComponent(pluginName); } if (pluginType === 'backend' || pluginType === 'fullstack') { files[`plugins/${pluginName}-backend/src/plugin.ts`] = this.generateBackendPlugin(pluginName); files[`plugins/${pluginName}-backend/src/router.ts`] = this.generateRouter(); } return files; } private generatePackageJson(pluginName: string, pluginType: string) { return JSON.stringify({ name: `@internal/${pluginName}${pluginType === 'backend' ? '-backend' : ''}`, version: '0.1.0', main: 'src/index.ts', types: 'src/index.ts', license: 'Apache-2.0', dependencies: pluginType === 'frontend' ? { '@backstage/core-components': '^0.14.0', '@backstage/core-plugin-api': '^1.9.0', '@backstage/theme': '^0.5.0', 'react': '^17.0.2 || ^18.0.0', 'react-router-dom': '^6.3.0' } : { '@backstage/backend-common': '^0.23.0', '@backstage/backend-plugin-api': '^0.7.0', 'express': '^4.17.1', 'express-promise-router': '^4.1.0' } }, null, 2); } private generateIndexFile(pluginType: string) { if (pluginType === 'frontend') { return `export { ${pluginType}Plugin as default } from './plugin';`; } return `export * from './plugin';`; } private generateFrontendPlugin(pluginName: string) { return ` import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; export const ${pluginName}Plugin = createPlugin({ id: '${pluginName}', routes: { root: rootRouteRef, }, }); export const ${pluginName}Page = ${pluginName}Plugin.provide( createRoutableExtension({ name: '${pluginName}Page', component: () => import('./components/ExampleComponent').then(m => m.ExampleComponent), mountPoint: rootRouteRef, }), );`; } private generateRoutes() { return `import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ id: 'root', });`; } private generateExampleComponent(pluginName: string) { return ` import React from 'react'; import { Page, Header, Content } from '@backstage/core-components'; export const ExampleComponent = () => ( <Page themeId="tool"> <Header title="${pluginName}" subtitle="Welcome to ${pluginName}!" /> <Content> <div>Your plugin content goes here!</div> </Content> </Page> );`; } private generateBackendPlugin(pluginName: string) { return ` import { createBackendPlugin } from '@backstage/backend-plugin-api'; import { createRouter } from './router'; export const ${pluginName}Plugin = createBackendPlugin({ pluginId: '${pluginName}', register(env) { env.registerInit({ deps: { httpRouter: coreServices.httpRouter, logger: coreServices.logger, }, async init({ httpRouter, logger }) { httpRouter.use(await createRouter({ logger })); }, }); }, });`; } private generateRouter() { return ` import { Router } from 'express'; import { Logger } from 'winston'; export interface RouterOptions { logger: Logger; } export async function createRouter(options: RouterOptions): Promise<Router> { const { logger } = options; const router = Router(); router.get('/health', (_, response) => { logger.info('PONG!'); response.json({ status: 'ok' }); }); return router; }`; } private generatePluginCommands(pluginType: string, pluginName: string) { return [ `yarn create @backstage/plugin --${pluginType} ${pluginName}`, `cd plugins/${pluginName}`, 'yarn install', 'yarn build', 'yarn test' ]; } private generatePluginDependencies(pluginType: string, features: string[]) { const baseDeps = pluginType === 'frontend' ? [ '@backstage/core-components', '@backstage/core-plugin-api', '@backstage/theme' ] : [ '@backstage/backend-common', '@backstage/backend-plugin-api' ]; const featureDeps: { [key: string]: string[] } = { 'api-client': ['@backstage/catalog-client'], 'entity-provider': ['@backstage/plugin-catalog-backend'], 'scaffolder-action': ['@backstage/plugin-scaffolder-backend'], 'search-collator': ['@backstage/plugin-search-backend-node'] }; const additionalDeps = features.flatMap(feature => featureDeps[feature] || []); return [...baseDeps, ...additionalDeps]; } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Backstage MCP server running on stdio'); // Keep process alive - MCP servers should run indefinitely process.on('SIGINT', () => { console.error('Shutting down Backstage MCP server...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('Shutting down Backstage MCP server...'); process.exit(0); }); } } const server = new BackstageMCPServer(); server.start().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PawelWaj/MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server