├── .husky └── pre-commit ├── src ├── mcp │ └── tools │ │ ├── context.ts │ │ ├── tool-registry.ts │ │ └── markmap-tools.ts ├── utils │ └── logger.ts ├── common │ └── registry-base.ts ├── index.ts └── markmap │ └── createMarkmap.ts ├── vitest.config.ts ├── prettier.config.js ├── tsconfig.json ├── .github └── workflows │ ├── ci.yml │ ├── npm-publish.yml │ └── prepare-release.yml ├── smithery.yaml ├── Dockerfile ├── eslint.config.js ├── tests └── services │ └── markmap.test.ts ├── LICENSE ├── .gitignore ├── package.json ├── README_zh-CN.md ├── README.md └── docs ├── markmap_zh.svg └── markmap.svg /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | npm run build 3 | -------------------------------------------------------------------------------- /src/mcp/tools/context.ts: -------------------------------------------------------------------------------- 1 | export interface MarkmapMcpContext { 2 | /** 3 | * The directory where the generated markmap file will be saved. 4 | */ 5 | output: string; 6 | } 7 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: "node", 6 | include: ["tests/**/*.test.ts"], 7 | globals: true 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import { pino } from "pino"; 2 | 3 | const logger = pino({ 4 | level: "info", 5 | formatters: { 6 | level: (label: string) => ({ level: label.toUpperCase() }) 7 | }, 8 | timestamp: () => `,"timestamp":"${new Date().toISOString()}"`, 9 | messageKey: "message", 10 | nestedKey: "payload" 11 | }); 12 | 13 | export default logger; 14 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | useTabs: false, 5 | trailingComma: "none", 6 | semi: true, 7 | singleQuote: false, 8 | overrides: [ 9 | { 10 | files: ["**/*.md", "**/*.yml"], 11 | options: { 12 | tabWidth: 2 13 | } 14 | } 15 | ], 16 | plugins: ["prettier-plugin-organize-imports"] 17 | }; 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "sourceMap": true, 14 | "resolveJsonModule": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "build"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Use Node.js 20 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 20 20 | cache: "npm" 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | - name: Run tests 26 | run: npm test 27 | 28 | - name: Build 29 | run: npm run build 30 | -------------------------------------------------------------------------------- /src/mcp/tools/tool-registry.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { RegistryBase } from "../../common/registry-base.js"; 3 | import { MarkmapMcpContext } from "./context.js"; 4 | 5 | export abstract class ToolRegistry extends RegistryBase { 6 | constructor( 7 | protected server: McpServer, 8 | protected context: MarkmapMcpContext 9 | ) { 10 | super(server, context); 11 | } 12 | 13 | /** 14 | * Registers all applicable tools based on site version and authentication status. 15 | * This method follows a specific registration sequence to ensure proper tool organization. 16 | */ 17 | public registerTools(): void { 18 | this.register(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/registry-base.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { MarkmapMcpContext } from "../mcp/tools/context.js"; 3 | 4 | export abstract class RegistryBase { 5 | /** 6 | * Creates a new registry instance. 7 | * 8 | * @param server - The MCP server instance to register components with 9 | * @param context - The context object containing configuration and state information 10 | */ 11 | constructor( 12 | protected server: McpServer, 13 | protected context: MarkmapMcpContext 14 | ) {} 15 | 16 | /** 17 | * Registers all applicable components based on site version and authentication status. 18 | * This method follows a specific registration sequence to ensure proper component organization. 19 | */ 20 | public abstract register(): void; 21 | } 22 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: [] 9 | properties: 10 | output: 11 | type: string 12 | description: Output directory for mind map HTML files (optional). If not specified, the default is the system's temporary directory. 13 | commandFunction: 14 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 15 | |- 16 | (config) => ({ 17 | command: 'node', 18 | args: [ 19 | 'build/index.js', 20 | ...(config.output ? ['--output', config.output] : []) 21 | ] 22 | }) 23 | exampleConfig: 24 | output: /tmp/markmap 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS builder 2 | 3 | # Create app directory 4 | WORKDIR /app 5 | 6 | # Copy package.json and package-lock.json 7 | COPY package*.json ./ 8 | 9 | # Install dependencies, ignoring any prepare scripts 10 | RUN npm install --ignore-scripts 11 | 12 | # Copy the rest of the application code 13 | COPY src ./src/ 14 | COPY tsconfig.json ./ 15 | 16 | # Build the application 17 | RUN npm run build 18 | 19 | # Use Node.js 20 Alpine as the base image for the runtime stage 20 | FROM node:20-alpine AS runner 21 | 22 | # Set the working directory for the runtime stage 23 | WORKDIR /app 24 | 25 | # Copy the built application and dependencies from the builder stage 26 | COPY --from=builder /app/package*.json ./ 27 | COPY --from=builder /app/build ./build 28 | 29 | RUN npm install --ignore-scripts --omit=dev 30 | 31 | # Define environment variable for markmap data directory 32 | ENV MARKMAP_DIR=/data/markmap 33 | 34 | # Define the command to run when the container starts 35 | ENTRYPOINT ["node", "build/index.js"] 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import { rules as prettierRules } from "eslint-config-prettier"; 3 | import { defineConfig } from "eslint/config"; 4 | import globals from "globals"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default defineConfig([ 8 | { 9 | files: ["**/*.{js,mjs,cjs,ts}"], 10 | plugins: { js }, 11 | extends: ["js/recommended"], 12 | rules: { 13 | ...prettierRules 14 | }, 15 | languageOptions: { globals: globals.browser } 16 | }, 17 | tseslint.configs.recommended, 18 | { 19 | files: ["**/*.ts"], 20 | rules: { 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-unused-vars": [ 23 | "warn", 24 | { 25 | argsIgnorePattern: "^_", 26 | varsIgnorePattern: "^_", 27 | ignoreRestSiblings: true 28 | } 29 | ] 30 | } 31 | } 32 | ]); 33 | -------------------------------------------------------------------------------- /tests/services/markmap.test.ts: -------------------------------------------------------------------------------- 1 | import { Transformer, builtInPlugins } from "markmap-lib"; 2 | import { fillTemplate } from "markmap-render"; 3 | import { describe, expect, it } from "vitest"; 4 | 5 | describe("Markmap Lib Test", () => { 6 | describe("export the markdown to HTML", () => { 7 | const testMarkdownContent = `# Test Mindmap 8 | - Topic 1 9 | - Subtopic 1.1 10 | - Subtopic 1.2 11 | - Topic 2 12 | - Subtopic 2.1 13 | - Detail 2.1.1`; 14 | 15 | it("should return HTML string", async () => { 16 | const transformer = new Transformer([...builtInPlugins]); 17 | const { root, features } = 18 | transformer.transform(testMarkdownContent); 19 | const assets = transformer.getUsedAssets(features); 20 | const html = fillTemplate(root, assets, undefined); 21 | 22 | expect(html).toBeDefined(); 23 | expect(html).toContain(""); 24 | expect(html).toContain("Test Mindmap"); 25 | }, 10000); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Zhichao (@jinzcdev) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish-npm: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | registry-url: https://registry.npmjs.org/ 19 | cache: "npm" 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Run tests 23 | run: npm test 24 | - name: Build package 25 | run: npm run build 26 | - name: Check package version matches release 27 | run: | 28 | PKG_VERSION=$(node -p "require('./package.json').version") 29 | GITHUB_REF_VERSION=${GITHUB_REF#refs/tags/v} 30 | if [ "$PKG_VERSION" != "$GITHUB_REF_VERSION" ]; then 31 | echo "::error::Package version ($PKG_VERSION) does not match release tag ($GITHUB_REF_VERSION)" 32 | exit 1 33 | fi 34 | - name: Publish to NPM 35 | run: npm publish --provenance --access public 36 | env: 37 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | # lib 79 | lib/ 80 | 81 | **/.DS_Store 82 | 83 | build/ 84 | .vscode/* 85 | !.vscode/launch.json 86 | 87 | tmp/ 88 | -------------------------------------------------------------------------------- /.github/workflows/prepare-release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Version to bump to (e.g. patch, minor, major, or specific version)" 8 | required: true 9 | default: "patch" 10 | 11 | jobs: 12 | prepare-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Update version 28 | run: | 29 | npm version ${{ github.event.inputs.version }} --no-git-tag-version 30 | NEW_VERSION=$(node -p "require('./package.json').version") 31 | echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV 32 | 33 | - name: Generate changelog 34 | id: changelog 35 | run: | 36 | npx conventional-changelog-cli -p angular -i CHANGELOG.md -s 37 | 38 | - name: Create Pull Request 39 | uses: peter-evans/create-pull-request@v7 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | commit-message: "chore(release): v${{ env.NEW_VERSION }}" 43 | title: "chore(release): prepare release v${{ env.NEW_VERSION }}" 44 | body: | 45 | Prepare release v${{ env.NEW_VERSION }} 46 | 47 | - Update version in package.json 48 | - Update CHANGELOG.md 49 | base: main 50 | branch: release/v${{ env.NEW_VERSION }} 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinzcdev/markmap-mcp-server", 3 | "description": "MCP server for converting Markdown to interactive mind maps with export support (PNG/JPG/SVG)", 4 | "version": "0.1.1", 5 | "author": "jinzcdev", 6 | "main": "./build/index.js", 7 | "keywords": [ 8 | "markmap", 9 | "mindmap", 10 | "mindnode", 11 | "xmind", 12 | "mcp" 13 | ], 14 | "scripts": { 15 | "test": "vitest run | pino-pretty", 16 | "test:watch": "vitest watch", 17 | "build": "tsc && chmod u+x build/index.js", 18 | "start": "node build/index.js", 19 | "dev": "tsc-watch --onSuccess \"node build/index.js\" | pino-pretty", 20 | "format": "prettier --write . --ignore-path .gitignore", 21 | "prepare": "husky" 22 | }, 23 | "bin": { 24 | "markmap-mcp-server": "build/index.js" 25 | }, 26 | "type": "module", 27 | "files": [ 28 | "build" 29 | ], 30 | "license": "MIT", 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/jinzcdev/markmap-mcp-server.git" 34 | }, 35 | "lint-staged": { 36 | "*.{js,ts,jsx,tsx}": [ 37 | "prettier --write", 38 | "eslint --fix" 39 | ], 40 | "*.{md,json,yml,yaml,html,css}": [ 41 | "prettier --write" 42 | ] 43 | }, 44 | "dependencies": { 45 | "@modelcontextprotocol/sdk": "^1.8.0", 46 | "minimist": "^1.2.8", 47 | "markmap-cli": "^0.18.11", 48 | "pino": "^9.6.0", 49 | "zod": "^3.24.2", 50 | "open": "^10.1.0" 51 | }, 52 | "devDependencies": { 53 | "@eslint/js": "^9.24.0", 54 | "@types/minimist": "^1.2.5", 55 | "@types/node": "^22.14.0", 56 | "esbuild": "^0.25.2", 57 | "eslint": "^9.24.0", 58 | "eslint-config-prettier": "^10.1.2", 59 | "eslint-plugin-prettier": "^5.2.6", 60 | "globals": "^16.0.0", 61 | "husky": "^9.1.7", 62 | "lint-staged": "^15.5.1", 63 | "pino-pretty": "^13.0.0", 64 | "prettier": "^3.5.3", 65 | "prettier-plugin-organize-imports": "^4.1.0", 66 | "tsc-watch": "6.2.1", 67 | "typescript": "^5.8.3", 68 | "typescript-eslint": "^8.29.1", 69 | "vitest": "^3.1.3", 70 | "vite": "^6.3.5" 71 | }, 72 | "optionalDependencies": { 73 | "@rollup/rollup-linux-x64-gnu": "^4.40.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import minimist from "minimist"; 5 | import { existsSync, mkdirSync } from "node:fs"; 6 | import { tmpdir } from "node:os"; 7 | import { join } from "node:path"; 8 | import { registerMarkmapTools } from "./mcp/tools/markmap-tools.js"; 9 | import logger from "./utils/logger.js"; 10 | 11 | /** 12 | * Parses and validates command line arguments for the Markmap MCP Server. 13 | * 14 | * @returns Configuration object with input and output file options 15 | */ 16 | function parseArgs() { 17 | const args = minimist(process.argv.slice(2), { 18 | string: ["output"], 19 | boolean: ["help"], 20 | alias: { 21 | o: "output", 22 | h: "help" 23 | } 24 | }); 25 | 26 | if (args.help) { 27 | logger.info(`Markmap MCP Server - Mind map generator for Markdown 28 | 29 | Usage: markmap-mcp-server [options] 30 | 31 | Options: 32 | --output, -o Output HTML file directory 33 | --help, -h Show this help message`); 34 | process.exit(0); 35 | } 36 | 37 | return { 38 | output: args.output || process.env.MARKMAP_DIR, 39 | open: args.open || false 40 | }; 41 | } 42 | 43 | /** 44 | * Main function that initializes and starts the Markmap MCP Server. 45 | * This function reads the input markdown file and generates a mind map. 46 | */ 47 | async function main() { 48 | const options = parseArgs(); 49 | 50 | const server = new McpServer({ 51 | name: "Markmap MCP Server", 52 | version: "0.1.0" 53 | }); 54 | 55 | let outputPath; 56 | if (options.output) { 57 | if (!existsSync(options.output)) { 58 | mkdirSync(options.output, { recursive: true }); 59 | } 60 | outputPath = options.output; 61 | } else { 62 | const tempDir = join(tmpdir(), "markmap"); 63 | if (!existsSync(tempDir)) { 64 | mkdirSync(tempDir, { recursive: true }); 65 | } 66 | outputPath = tempDir; 67 | } 68 | 69 | registerMarkmapTools(server, { output: outputPath }); 70 | 71 | const transport = new StdioServerTransport(); 72 | await server.connect(transport); 73 | } 74 | 75 | main().catch((error) => { 76 | logger.error("Failed to start Markmap MCP Server: %s", error); 77 | process.exit(1); 78 | }); 79 | -------------------------------------------------------------------------------- /src/mcp/tools/markmap-tools.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { join } from "path"; 3 | import { z } from "zod"; 4 | import { createMarkmap } from "../../markmap/createMarkmap.js"; 5 | import { MarkmapMcpContext } from "./context.js"; 6 | import { ToolRegistry } from "./tool-registry.js"; 7 | 8 | export class MarkmapToolRegistry extends ToolRegistry { 9 | public register(): void { 10 | this.server.tool( 11 | "markdown_to_mindmap", 12 | "Convert a Markdown document into an interactive mind map", 13 | { 14 | markdown: z 15 | .string() 16 | .describe("Markdown content to convert into a mind map"), 17 | open: z 18 | .boolean() 19 | .default(false) 20 | .describe( 21 | "Whether to open the generated mind map in a browser (default: false)" 22 | ) 23 | }, 24 | async ({ markdown, open }) => { 25 | try { 26 | const filename = `markmap-${Date.now()}.html`; 27 | const outputPath = join(this.context.output, filename); 28 | 29 | const result = await createMarkmap({ 30 | content: markdown, 31 | output: outputPath, 32 | openIt: open 33 | }); 34 | 35 | return { 36 | content: [ 37 | { 38 | type: "text", 39 | text: JSON.stringify({ 40 | filePath: result.filePath 41 | }) 42 | } 43 | ] 44 | }; 45 | } catch (error: any) { 46 | return { 47 | content: [ 48 | { 49 | type: "text", 50 | text: JSON.stringify({ 51 | error: "Failed to generate markmap", 52 | message: error.message 53 | }) 54 | } 55 | ] 56 | }; 57 | } 58 | } 59 | ); 60 | } 61 | } 62 | 63 | /** 64 | * Registers Markmap tools with the provided server and context. 65 | * @param server - The MCP server instance to register tools with 66 | * @param context - The context object containing configuration and state information 67 | */ 68 | export function registerMarkmapTools( 69 | server: McpServer, 70 | context: MarkmapMcpContext 71 | ): void { 72 | const registry = new MarkmapToolRegistry(server, context); 73 | registry.register(); 74 | } 75 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # Markmap MCP 服务器 2 | 3 | ![Sample Mindmap](./docs/markmap_zh.svg) 4 | 5 | [![NPM Version](https://img.shields.io/npm/v/@jinzcdev/markmap-mcp-server.svg)](https://www.npmjs.com/package/@jinzcdev/markmap-mcp-server) 6 | [![GitHub License](https://img.shields.io/github/license/jinzcdev/markmap-mcp-server.svg)](LICENSE) 7 | [![Smithery Badge](https://smithery.ai/badge/@jinzcdev/markmap-mcp-server)](https://smithery.ai/server/@jinzcdev/markmap-mcp-server) 8 | [![English Doc](https://img.shields.io/badge/English-Click-blue)](README.md) 9 | [![Stars](https://img.shields.io/github/stars/jinzcdev/markmap-mcp-server)](https://github.com/jinzcdev/markmap-mcp-server) 10 | 11 | Markmap MCP Server 基于 [模型上下文协议 (MCP)](https://modelcontextprotocol.io/introduction),可将 Markdown 文本一键转换为交互式思维导图,底层采用开源项目 [markmap](https://github.com/markmap/markmap)。生成的思维导图支持丰富的交互操作,并可导出为多种图片格式。 12 | 13 | > 🎉 **探索更多思维导图工具** 14 | > 15 | > 试试 [MarkXMind](https://github.com/jinzcdev/markxmind) - 一款使用简洁的 XMindMark 语法创建复杂思维导图的在线编辑器。支持实时预览、多格式导出(.xmind/.svg/.png)、导入现有 XMind 文件。[立即体验](https://markxmind.js.org/)! 16 | 17 | ## 特性 18 | 19 | - 🌠 **Markdown 转思维导图**:将 Markdown 文本转换为交互式思维导图 20 | - 🖼️ **多格式导出**:支持导出为 PNG、JPG 和 SVG 格式的图片 21 | - 🔄 **交互式操作**:支持缩放、展开/折叠节点等交互功能 22 | - 📋 **Markdown 复制**:一键复制原始 Markdown 内容 23 | - 🌐 **自动浏览器预览**:可选择自动在浏览器中打开生成的思维导图 24 | 25 | ## 前提条件 26 | 27 | 1. Node.js (v20 或以上) 28 | 29 | ## 安装 30 | 31 | ### 手动安装 32 | 33 | ```bash 34 | # 从 npm 安装 35 | npm install @jinzcdev/markmap-mcp-server -g 36 | 37 | # 基本运行 38 | npx -y @jinzcdev/markmap-mcp-server 39 | 40 | # 指定输出目录 41 | npx -y @jinzcdev/markmap-mcp-server --output /path/to/output/directory 42 | ``` 43 | 44 | 或者,您可以克隆仓库并在本地运行: 45 | 46 | ```bash 47 | # 克隆仓库 48 | git clone https://github.com/jinzcdev/markmap-mcp-server.git 49 | 50 | # 导航到项目目录 51 | cd markmap-mcp-server 52 | 53 | # 构建项目 54 | npm install && npm run build 55 | 56 | # 运行服务器 57 | node build/index.js 58 | ``` 59 | 60 | ## 使用方法 61 | 62 | 添加以下配置到您的 MCP 客户端配置文件中: 63 | 64 | ```json 65 | { 66 | "mcpServers": { 67 | "markmap": { 68 | "type": "stdio", 69 | "command": "npx", 70 | "args": ["-y", "@jinzcdev/markmap-mcp-server"], 71 | "env": { 72 | "MARKMAP_DIR": "/path/to/output/directory" 73 | } 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | > [!TIP] 80 | > 81 | > 服务支持以下环境变量: 82 | > 83 | > - `MARKMAP_DIR`:指定思维导图的输出目录(可选,默认为系统临时目录) 84 | > 85 | > **优先级说明**: 86 | > 87 | > 当同时指定命令行参数 `--output` 和环境变量 `MARKMAP_DIR` 时,命令行参数优先。 88 | 89 | ## 可用工具 90 | 91 | ### markdown-to-mindmap 92 | 93 | 将 Markdown 文本转换为交互式思维导图。 94 | 95 | **参数:** 96 | 97 | - `markdown`:要转换的 Markdown 内容(必填字符串) 98 | - `open`:是否在浏览器中自动打开生成的思维导图(可选布尔值,默认为 false) 99 | 100 | **返回值:** 101 | 102 | ```json 103 | { 104 | "content": [ 105 | { 106 | "type": "text", 107 | "text": "JSON_DATA_OF_MINDMAP_FILEPATH" 108 | } 109 | ] 110 | } 111 | ``` 112 | 113 | ## 许可证 114 | 115 | 本项目采用 [MIT](./LICENSE) 许可证。 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Markmap MCP Server 2 | 3 | ![Sample Mindmap](./docs/markmap.svg) 4 | 5 | [![NPM Version](https://img.shields.io/npm/v/@jinzcdev/markmap-mcp-server.svg)](https://www.npmjs.com/package/@jinzcdev/markmap-mcp-server) 6 | [![GitHub License](https://img.shields.io/github/license/jinzcdev/markmap-mcp-server.svg)](LICENSE) 7 | [![Smithery Badge](https://smithery.ai/badge/@jinzcdev/markmap-mcp-server)](https://smithery.ai/server/@jinzcdev/markmap-mcp-server) 8 | [![中文文档](https://img.shields.io/badge/中文文档-点击查看-blue)](README_zh-CN.md) 9 | [![Stars](https://img.shields.io/github/stars/jinzcdev/markmap-mcp-server)](https://github.com/jinzcdev/markmap-mcp-server) 10 | 11 | Markmap MCP Server is based on the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) that allows one-click conversion of Markdown text to interactive mind maps, built on the open source project [markmap](https://github.com/markmap/markmap). The generated mind maps support rich interactive operations and can be exported in various image formats. 12 | 13 | > 🎉 **Explore More Mind Mapping Tools** 14 | > 15 | > Try [MarkXMind](https://github.com/jinzcdev/markxmind) - An online editor that creates complex mind maps using simple XMindMark syntax. It supports real-time preview, multi-format export (.xmind/.svg/.png), importing existing XMind files. [Try it now](https://markxmind.js.org/)! 16 | 17 | ## Features 18 | 19 | - 🌠 **Markdown to Mind Map**: Convert Markdown text to interactive mind maps 20 | - 🖼️ **Multi-format Export**: Support for exporting as PNG, JPG, and SVG images 21 | - 🔄 **Interactive Operations**: Support for zooming, expanding/collapsing nodes, and other interactive features 22 | - 📋 **Markdown Copy**: One-click copy of the original Markdown content 23 | - 🌐 **Automatic Browser Preview**: Option to automatically open generated mind maps in the browser 24 | 25 | ## Prerequisites 26 | 27 | 1. Node.js (v20 or above) 28 | 29 | ## Installation 30 | 31 | ### Installing via Smithery 32 | 33 | To install Markmap MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@jinzcdev/markmap-mcp-server): 34 | 35 | ```bash 36 | npx -y @smithery/cli install @jinzcdev/markmap-mcp-server --client claude 37 | ``` 38 | 39 | ### Manual Installation 40 | 41 | ```bash 42 | # Install from npm 43 | npm install @jinzcdev/markmap-mcp-server -g 44 | 45 | # Basic run 46 | npx -y @jinzcdev/markmap-mcp-server 47 | 48 | # Specify output directory 49 | npx -y @jinzcdev/markmap-mcp-server --output /path/to/output/directory 50 | ``` 51 | 52 | Alternatively, you can clone the repository and run locally: 53 | 54 | ```bash 55 | # Clone the repository 56 | git clone https://github.com/jinzcdev/markmap-mcp-server.git 57 | 58 | # Navigate to the project directory 59 | cd markmap-mcp-server 60 | 61 | # Build project 62 | npm install && npm run build 63 | 64 | # Run the server 65 | node build/index.js 66 | ``` 67 | 68 | ## Usage 69 | 70 | Add the following configuration to your MCP client configuration file: 71 | 72 | ```json 73 | { 74 | "mcpServers": { 75 | "markmap": { 76 | "type": "stdio", 77 | "command": "npx", 78 | "args": ["-y", "@jinzcdev/markmap-mcp-server"], 79 | "env": { 80 | "MARKMAP_DIR": "/path/to/output/directory" 81 | } 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | > [!TIP] 88 | > 89 | > The service supports the following environment variables: 90 | > 91 | > - `MARKMAP_DIR`: Specify the output directory for mind maps (optional, defaults to system temp directory) 92 | > 93 | > **Priority Note**: 94 | > 95 | > When both the `--output` command line argument and the `MARKMAP_DIR` environment variable are specified, the command line argument takes precedence. 96 | 97 | ## Available Tools 98 | 99 | ### markdown-to-mindmap 100 | 101 | Convert Markdown text into an interactive mind map. 102 | 103 | **Parameters:** 104 | 105 | - `markdown`: The Markdown content to convert (required string) 106 | - `open`: Whether to automatically open the generated mind map in the browser (optional boolean, default is false) 107 | 108 | **Return Value:** 109 | 110 | ```json 111 | { 112 | "content": [ 113 | { 114 | "type": "text", 115 | "text": "JSON_DATA_OF_MINDMAP_FILEPATH" 116 | } 117 | ] 118 | } 119 | ``` 120 | 121 | ## License 122 | 123 | This project is licensed under the [MIT](./LICENSE) License. 124 | -------------------------------------------------------------------------------- /src/markmap/createMarkmap.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from "crypto"; 2 | import { promises as fs } from "fs"; 3 | import { tmpdir } from "os"; 4 | import { join } from "path"; 5 | 6 | import { Transformer, builtInPlugins } from "markmap-lib"; 7 | import { fillTemplate } from "markmap-render"; 8 | 9 | import open from "open"; 10 | 11 | interface CreateMarkmapOptions { 12 | /** 13 | * Markdown content to be converted into a mind map 14 | */ 15 | content: string; 16 | /** 17 | * Output file path, if not provided, a temporary file will be created 18 | */ 19 | output?: string; 20 | /** 21 | * Whether to open the output file after generation 22 | * @default false 23 | */ 24 | openIt?: boolean; 25 | } 26 | 27 | interface CreateMarkmapResult { 28 | /** 29 | * Path to the generated HTML file 30 | */ 31 | filePath: string; 32 | /** 33 | * Content of the generated HTML file 34 | */ 35 | content: string; 36 | } 37 | 38 | /** 39 | * Creates a mind map from Markdown content with additional features. 40 | * 41 | * @param options Options for creating the mind map 42 | * @returns Promise containing the generated mind map file path and content 43 | */ 44 | export async function createMarkmap( 45 | options: CreateMarkmapOptions 46 | ): Promise { 47 | const { content, output, openIt = false } = options; 48 | 49 | // If no output path is provided, generate a default file path in the temp directory 50 | const filePath = output || join(tmpdir(), `markmap-${randomUUID()}.html`); 51 | 52 | const transformer = new Transformer([...builtInPlugins]); 53 | const { root, features } = transformer.transform(content); 54 | const assets = transformer.getUsedAssets(features); 55 | const html = fillTemplate(root, assets, undefined); 56 | 57 | // Add markmap-toolbar related code 58 | const toolbarCode = ` 59 | 63 | 64 | 65 | 87 | `; 88 | 89 | // Add scripts and styles for additional features 90 | const additionalCode = ` 91 | 92 | 93 | 94 | 95 | 96 | 97 | 154 | 155 | 276 | `; 277 | 278 | const updatedContent = html.replace( 279 | "", 280 | `${toolbarCode}\n${additionalCode}\n` 281 | ); 282 | 283 | await fs.writeFile(filePath, updatedContent); 284 | 285 | if (openIt) { 286 | await open(filePath); 287 | } 288 | 289 | return { 290 | filePath, 291 | content: updatedContent 292 | }; 293 | } 294 | -------------------------------------------------------------------------------- /docs/markmap_zh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
🌠 Markdown 转思维导图:将 Markdown 文本转换为交互式思维导图
28 |
29 |
30 |
31 | 32 | 33 | 34 |
35 |
🖼️ 多格式导出:支持导出为 PNG、JPG 和 SVG 格式的图片
36 |
37 |
38 |
39 | 40 | 41 | 42 |
43 |
🔄 交互式操作:支持缩放、展开/折叠节点等交互功能
44 |
45 |
46 |
47 | 48 | 49 | 50 |
51 |
📋 Markdown 复制:一键复制原始 Markdown 内容
52 |
53 |
54 |
55 | 56 | 57 | 58 |
59 |
🌐 自动浏览器预览:可选择自动在浏览器中打开生成的思维导图
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 |
68 |
特性
69 |
70 |
71 |
72 | 73 | 74 | 75 |
76 |
1. Node.js 运行环境
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 |
85 |
前提条件
86 |
87 |
88 |
89 | 90 | 91 | 92 |
93 |
手动安装
94 |
95 |
96 |
97 | 98 | 99 | 100 | 101 |
102 |
安装
103 |
104 |
105 |
106 | 107 | 108 | 109 |
110 |
使用方法
111 |
112 |
113 |
114 | 115 | 116 | 117 |
118 |
markdown:要转换的 Markdown 内容(必填字符串)
119 |
120 |
121 |
122 | 123 | 124 | 125 |
126 |
open:是否在浏览器中自动打开生成的思维导图(可选布尔值,默认为 false)
127 |
128 |
129 |
130 | 131 | 132 | 133 | 134 |
135 |
markdown-to-mindmap
136 |
137 |
138 |
139 | 140 | 141 | 142 | 143 |
144 |
可用工具
145 |
146 |
147 |
148 | 149 | 150 | 151 |
152 |
许可证
153 |
154 |
155 |
156 | 157 | 158 | 159 | 160 |
161 |
Markmap MCP 服务器
162 |
163 |
164 |
165 |
166 |
-------------------------------------------------------------------------------- /docs/markmap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
🌠 Markdown to Mind Map: Convert Markdown text to interactive mind maps
27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
🖼️ Multi-format Export: Support for exporting as PNG, JPG, and SVG images
35 |
36 |
37 |
38 | 39 | 40 | 41 |
42 |
🔄 Interactive Operations: Support for zooming, expanding/collapsing nodes, and other interactive features
43 |
44 |
45 |
46 | 47 | 48 | 49 |
50 |
📋 Markdown Copy: One-click copy of the original Markdown content
51 |
52 |
53 |
54 | 55 | 56 | 57 |
58 |
🌐 Automatic Browser Preview: Option to automatically open generated mind maps in the browser
59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 |
67 |
Features
68 |
69 |
70 |
71 | 72 | 73 | 74 |
75 |
1. Node.js runtime environment
76 |
77 |
78 |
79 | 80 | 81 | 82 | 83 |
84 |
Prerequisites
85 |
86 |
87 |
88 | 89 | 90 | 91 |
92 |
Manual Installation
93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 |
101 |
Installation
102 |
103 |
104 |
105 | 106 | 107 | 108 |
109 |
Usage
110 |
111 |
112 |
113 | 114 | 115 | 116 |
117 |
118 | markdown: The Markdown content to convert (required string)
119 |
120 |
121 |
122 | 123 | 124 | 125 |
126 |
127 | open: Whether to automatically open the generated mind map in the browser (optional boolean, default is false)
128 |
129 |
130 |
131 | 132 | 133 | 134 | 135 |
136 |
markdown-to-mindmap
137 |
138 |
139 |
140 | 141 | 142 | 143 | 144 |
145 |
Available Tools
146 |
147 |
148 |
149 | 150 | 151 | 152 |
153 |
License
154 |
155 |
156 |
157 | 158 | 159 | 160 | 161 |
162 |
Markmap MCP Server
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------