Skip to main content
Glama

Xmind Generator MCP Server

by BangyiZhang
index.ts8.29 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { exec } from 'child_process'; import { createRequire } from 'module'; // Create a require function for ES modules const require = createRequire(import.meta.url); // Check if xmind-generator is installed let xmindGeneratorInstalled = false; try { // Try to require the package instead of using npm list require.resolve('xmind-generator'); xmindGeneratorInstalled = true; // xmind-generator package found } catch (error) { console.error('xmind-generator package not found in Node.js module resolution paths.'); console.error('Current module paths:'); console.error(module.paths.join('\n')); console.error('Please run: npm install xmind-generator@1.0.1 --save in the project directory'); // Don't exit immediately to allow the error to be reported back to the client // process.exit(1); } // Create a temporary directory for storing generated XMind files const TEMP_DIR = path.join(os.tmpdir(), 'xmind-generator-mcp'); if (!fs.existsSync(TEMP_DIR)) { fs.mkdirSync(TEMP_DIR, { recursive: true }); } // Define the schema for our mind map generation tool const RelationshipSchema = z.object({ title: z.string().describe('The title of the relationship'), from: z.string().describe('The reference ID of the source topic'), to: z.string().describe('The reference ID of the target topic') }); const TopicSchema: z.ZodType<any> = z.lazy(() => z.object({ title: z.string().describe('The title of the topic'), ref: z.string().optional().describe('Optional reference ID for the topic'), note: z.string().optional().describe('Optional note for the topic'), labels: z.array(z.string()).optional().describe('Optional array of labels for the topic'), markers: z.array(z.string()).optional().describe('Optional array of markers for the topic (format: "Category.name", e.g., "Arrow.refresh")'), children: z.array(TopicSchema).optional().describe('Optional array of child topics'), relationships: z.array(RelationshipSchema).optional().describe('Optional array of relationships for the topic') })); const GenerateMindMapSchema = z.object({ title: z.string().describe('The title of the mind map (root topic)'), topics: z.array(TopicSchema).describe('Array of topics to include in the mind map'), filename: z.string().describe('The filename for the XMind file (without path or extension)'), outputPath: z.string().optional().describe('Optional custom output path for the XMind file. If not provided, the file will be created in the temporary directory.'), relationships: z.array(RelationshipSchema).optional().describe('Optional array of relationships between topics') }); type TopicData = z.infer<typeof TopicSchema>; type GenerateMindMapParams = z.infer<typeof GenerateMindMapSchema>; // Create server instance const server = new McpServer({ name: 'xmind-generator', version: '0.1.0' }); // Register the generate-mind-map tool server.tool( 'generate-mind-map', GenerateMindMapSchema.shape, async (params: GenerateMindMapParams) => { try { // Check if xmind-generator is available if (!xmindGeneratorInstalled) { return { content: [{ type: 'text', text: 'The xmind-generator package is not properly installed. Please run: npm install xmind-generator@1.0.1 --save in the project directory.' }], isError: true }; } // Import xmind-generator using the require function const xmindGenerator = require('xmind-generator'); const { Topic, RootTopic, Workbook, writeLocalFile } = xmindGenerator; // Define the output path using the required filename parameter // Sanitize the filename to ensure it's valid for both Windows and Unix systems // Only filter out truly invalid characters: \ / : * ? " < > | const sanitizedFilename = params.filename.replace(/[\\/:*?"<>|]/g, '-'); // Get output path with priority: // 1. Parameter provided in the tool call (params.outputPath) // 2. Environment variable (process.env.outputPath) // 3. Default temporary directory (TEMP_DIR) const baseOutputPath = params.outputPath || process.env.outputPath || TEMP_DIR; // If baseOutputPath is a directory, append the filename // If it's a file path (has extension), use it directly let outputPath: string; if (baseOutputPath.endsWith('.xmind')) { outputPath = baseOutputPath; } else { // Assume it's a directory path outputPath = path.join(baseOutputPath, `${sanitizedFilename}.xmind`); } // Ensure the output directory exists const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // Create root topic const rootTopic = RootTopic(params.title); // Add relationships if provided if (params.relationships && params.relationships.length > 0) { rootTopic.relationships(params.relationships.map(rel => { return xmindGenerator.Relationship(rel.title, { from: rel.from, to: rel.to }); })); } // Helper function to build topics recursively function buildTopic(topicData: TopicData) { const topic = Topic(topicData.title); if (topicData.ref) { topic.ref(topicData.ref); } if (topicData.note) { topic.note(topicData.note); } if (topicData.labels && topicData.labels.length > 0) { topic.labels(topicData.labels); } if (topicData.markers && topicData.markers.length > 0) { // Convert marker strings to Marker objects const markers = topicData.markers.map((markerStr: string) => { const [category, name] = markerStr.split('.'); return xmindGenerator.Marker[category]?.[name] || markerStr; }); topic.markers(markers); } if (topicData.children && topicData.children.length > 0) { const children = topicData.children.map((child: TopicData) => buildTopic(child)); topic.children(children); } return topic; } // Build children if (params.topics && params.topics.length > 0) { const children = params.topics.map((topic: TopicData) => buildTopic(topic)); rootTopic.children(children); } // Create workbook const workbook = Workbook(rootTopic); // Write to file writeLocalFile(workbook, outputPath); // Check if we should automatically open the generated file // Default to true if environment variable is not set const autoOpenFile = process.env.autoOpenFile !== 'false'; if (autoOpenFile) { // Open the generated file with default application const platform = process.platform; const openCommand = platform === 'win32' ? 'start' : platform === 'darwin' ? 'open' : 'xdg-open'; exec(`${openCommand} "${outputPath}"`, (error) => { if (error) { console.error('Error opening file:', error); } }); } return { content: [{ type: 'text', text: `Mind map successfully generated and saved to: ${outputPath}` }] }; } catch (error) { console.error('Error generating mind map:', error); return { content: [{ type: 'text', text: `Error generating mind map: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Create a transport using standard IO for server communication const transport = new StdioServerTransport(); // Get environment configuration const envOutputPath = process.env.outputPath; const envAutoOpenFile = process.env.autoOpenFile; // Connect the server to the transport server.connect(transport).then(() => { // Server started successfully }).catch((error: Error) => { console.error('Failed to start server:', error); process.exit(1); });

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/BangyiZhang/xmind-generator-mcp'

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