@zengjing/xmind-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +574 -0
  3. package/dist/cli.d.ts +7 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +170 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/legacy-parser.d.ts +63 -0
  8. package/dist/core/legacy-parser.d.ts.map +1 -0
  9. package/dist/core/legacy-parser.js +139 -0
  10. package/dist/core/legacy-parser.js.map +1 -0
  11. package/dist/core/parser.d.ts +19 -0
  12. package/dist/core/parser.d.ts.map +1 -0
  13. package/dist/core/parser.js +111 -0
  14. package/dist/core/parser.js.map +1 -0
  15. package/dist/core/zen-parser.d.ts +15 -0
  16. package/dist/core/zen-parser.d.ts.map +1 -0
  17. package/dist/core/zen-parser.js +90 -0
  18. package/dist/core/zen-parser.js.map +1 -0
  19. package/dist/formatters/json-formatter.d.ts +69 -0
  20. package/dist/formatters/json-formatter.d.ts.map +1 -0
  21. package/dist/formatters/json-formatter.js +131 -0
  22. package/dist/formatters/json-formatter.js.map +1 -0
  23. package/dist/formatters/markdown-formatter.d.ts +40 -0
  24. package/dist/formatters/markdown-formatter.d.ts.map +1 -0
  25. package/dist/formatters/markdown-formatter.js +136 -0
  26. package/dist/formatters/markdown-formatter.js.map +1 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +117 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/model/schemas.d.ts +54 -0
  32. package/dist/model/schemas.d.ts.map +1 -0
  33. package/dist/model/schemas.js +64 -0
  34. package/dist/model/schemas.js.map +1 -0
  35. package/dist/model/types.d.ts +79 -0
  36. package/dist/model/types.d.ts.map +1 -0
  37. package/dist/model/types.js +6 -0
  38. package/dist/model/types.js.map +1 -0
  39. package/dist/tools/branch-tool.d.ts +20 -0
  40. package/dist/tools/branch-tool.d.ts.map +1 -0
  41. package/dist/tools/branch-tool.js +150 -0
  42. package/dist/tools/branch-tool.js.map +1 -0
  43. package/dist/tools/parse-tool.d.ts +27 -0
  44. package/dist/tools/parse-tool.d.ts.map +1 -0
  45. package/dist/tools/parse-tool.js +126 -0
  46. package/dist/tools/parse-tool.js.map +1 -0
  47. package/dist/tools/search-tool.d.ts +27 -0
  48. package/dist/tools/search-tool.d.ts.map +1 -0
  49. package/dist/tools/search-tool.js +179 -0
  50. package/dist/tools/search-tool.js.map +1 -0
  51. package/dist/utils/errors.d.ts +26 -0
  52. package/dist/utils/errors.d.ts.map +1 -0
  53. package/dist/utils/errors.js +41 -0
  54. package/dist/utils/errors.js.map +1 -0
  55. package/dist/utils/file-utils.d.ts +25 -0
  56. package/dist/utils/file-utils.d.ts.map +1 -0
  57. package/dist/utils/file-utils.js +58 -0
  58. package/dist/utils/file-utils.js.map +1 -0
  59. package/dist/utils/logger.d.ts +8 -0
  60. package/dist/utils/logger.d.ts.map +1 -0
  61. package/dist/utils/logger.js +23 -0
  62. package/dist/utils/logger.js.map +1 -0
  63. package/package.json +67 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * MCP Tool: search_xmind_nodes
3
+ * Search XMind nodes by keyword, label, or status marker.
4
+ */
5
+ import { parseXMindFile } from '../core/parser.js';
6
+ import { resolveFilePath } from '../utils/file-utils.js';
7
+ import { isXMindError } from '../utils/errors.js';
8
+ import { info, warn, error as logError } from '../utils/logger.js';
9
+ /**
10
+ * MCP Tool definition for search_xmind_nodes.
11
+ * Enables keyword/label/marker filtering with breadcrumb path resolution.
12
+ */
13
+ export const SEARCH_XMIND_TOOL = {
14
+ name: 'search_xmind_nodes',
15
+ description: 'Search for nodes in an XMind file by keyword (title/notes) or label. ' +
16
+ 'Returns matching nodes with breadcrumb paths. ' +
17
+ 'Useful for large mind maps to find specific topics without loading entire document.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ path: {
22
+ type: 'string',
23
+ description: 'Absolute file path to the .xmind file. Supports ~ for home directory expansion.',
24
+ },
25
+ query: {
26
+ type: 'string',
27
+ description: 'Search keyword or phrase to match in node titles, notes, or labels',
28
+ },
29
+ searchIn: {
30
+ type: 'array',
31
+ items: { type: 'string', enum: ['title', 'note', 'label'] },
32
+ default: ['title', 'note', 'label'],
33
+ description: 'Which fields to search in (default: all)',
34
+ },
35
+ caseSensitive: {
36
+ type: 'boolean',
37
+ default: false,
38
+ description: 'Enable case-sensitive matching',
39
+ },
40
+ },
41
+ required: ['path', 'query'],
42
+ },
43
+ };
44
+ /**
45
+ * Recursively search nodes and collect results with breadcrumb paths.
46
+ * Traverses the entire tree, building path from root to matching nodes.
47
+ *
48
+ * @param node - Current node being examined
49
+ * @param query - Search query string
50
+ * @param searchIn - Fields to search in (title, note, label)
51
+ * @param caseSensitive - Whether to use case-sensitive matching
52
+ * @param pathToRoot - Accumulated path from root (node IDs in reverse order)
53
+ * @returns Array of matching SearchResult objects with breadcrumb paths
54
+ */
55
+ function searchNode(node, query, searchIn = ['title', 'note', 'label'], caseSensitive = false, pathToRoot = []) {
56
+ const results = [];
57
+ // Normalize query based on case sensitivity setting
58
+ const normalizeQuery = caseSensitive ? (s) => s : (s) => s.toLowerCase();
59
+ const normalizedQuery = normalizeQuery(query);
60
+ // Check title field
61
+ if (searchIn.includes('title')) {
62
+ const normalizedTitle = normalizeQuery(node.title);
63
+ if (normalizedTitle.includes(normalizedQuery)) {
64
+ results.push({
65
+ node,
66
+ matchType: 'title',
67
+ matchText: node.title,
68
+ pathToRoot: [node.id, ...pathToRoot],
69
+ });
70
+ }
71
+ }
72
+ // Check note field
73
+ if (searchIn.includes('note') && node.note) {
74
+ const normalizedNote = normalizeQuery(node.note);
75
+ if (normalizedNote.includes(normalizedQuery)) {
76
+ // Truncate matched text to 100 chars for readability
77
+ const matchText = node.note.substring(0, 100) + (node.note.length > 100 ? '...' : '');
78
+ results.push({
79
+ node,
80
+ matchType: 'note',
81
+ matchText,
82
+ pathToRoot: [node.id, ...pathToRoot],
83
+ });
84
+ }
85
+ }
86
+ // Check labels field
87
+ if (searchIn.includes('label') && node.labels) {
88
+ for (const label of node.labels) {
89
+ const normalizedLabel = normalizeQuery(label);
90
+ if (normalizedLabel.includes(normalizedQuery)) {
91
+ results.push({
92
+ node,
93
+ matchType: 'label',
94
+ matchText: label,
95
+ pathToRoot: [node.id, ...pathToRoot],
96
+ });
97
+ }
98
+ }
99
+ }
100
+ // Recursively search children, building path as we go down the tree
101
+ for (const child of node.children) {
102
+ results.push(...searchNode(child, query, searchIn, caseSensitive, [node.id, ...pathToRoot]));
103
+ }
104
+ return results;
105
+ }
106
+ /**
107
+ * Format search results as readable text output.
108
+ * Includes match count, breadcrumb paths, match types, and preview of matched text.
109
+ *
110
+ * @param results - Array of SearchResult objects to format
111
+ * @returns Formatted string suitable for LLM consumption
112
+ */
113
+ function formatResults(results) {
114
+ if (results.length === 0) {
115
+ return 'No matches found.';
116
+ }
117
+ let output = `Found ${results.length} match${results.length !== 1 ? 'es' : ''}:\n\n`;
118
+ for (let i = 0; i < results.length; i++) {
119
+ const result = results[i];
120
+ // Reverse path to show root → ... → node order
121
+ const breadcrumb = result.pathToRoot.reverse().join(' > ');
122
+ output += `**${i + 1}. ${result.node.title}**\n`;
123
+ output += ` Path: ${breadcrumb}\n`;
124
+ output += ` Match: ${result.matchType} — "${result.matchText}"\n`;
125
+ // Include node note preview if available
126
+ if (result.node.note) {
127
+ const notePreview = result.node.note.substring(0, 80);
128
+ const noteSuffix = result.node.note.length > 80 ? '...' : '';
129
+ output += ` Note: ${notePreview}${noteSuffix}\n`;
130
+ }
131
+ output += '\n';
132
+ }
133
+ return output;
134
+ }
135
+ /**
136
+ * Executes the search_xmind_nodes tool.
137
+ * Parses the file, searches all sheets, and returns formatted results with breadcrumb paths.
138
+ *
139
+ * @param input - Tool input containing path, query, and optional search parameters
140
+ * @returns Formatted search results or error message
141
+ * @throws Does not throw; returns user-friendly error messages
142
+ */
143
+ export async function executeSearchTool(input) {
144
+ try {
145
+ // Step 1: Validate and resolve file path
146
+ info('search_xmind_nodes tool invoked', { path: input.path, query: input.query });
147
+ const resolvedPath = resolveFilePath(input.path);
148
+ // Step 2: Parse the XMind file
149
+ const parsed = await parseXMindFile(resolvedPath);
150
+ info('File parsed successfully for search', { sheetCount: parsed.sheets.length });
151
+ // Step 3: Check if file has content
152
+ if (parsed.sheets.length === 0) {
153
+ return 'No sheets found in the XMind file.';
154
+ }
155
+ // Step 4: Search across all sheets
156
+ const allResults = [];
157
+ for (const sheet of parsed.sheets) {
158
+ allResults.push(...searchNode(sheet.rootTopic, input.query, input.searchIn, input.caseSensitive));
159
+ }
160
+ // Step 5: Format and return results
161
+ const formatted = formatResults(allResults);
162
+ info('Search completed', { resultCount: allResults.length, query: input.query });
163
+ return formatted;
164
+ }
165
+ catch (err) {
166
+ // Step 6: Error handling with user-friendly messages
167
+ if (isXMindError(err)) {
168
+ warn('XMind error during search', { code: err.code, message: err.message });
169
+ return `Error: ${err.message}`;
170
+ }
171
+ if (err instanceof Error) {
172
+ logError('Unexpected error in search_xmind_nodes', err);
173
+ return `Error: ${err.message}`;
174
+ }
175
+ logError('Unknown error in search_xmind_nodes', err);
176
+ return 'Error: An unknown error occurred while searching the file.';
177
+ }
178
+ }
179
+ //# sourceMappingURL=search-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-tool.js","sourceRoot":"","sources":["../../src/tools/search-tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Custom error classes for XMind parsing and MCP operations.
3
+ * All errors include actionable messages to guide LLM recovery.
4
+ */
5
+ export declare class XMindError extends Error {
6
+ readonly code: string;
7
+ constructor(message: string, code?: string);
8
+ }
9
+ export declare class FileNotFoundError extends XMindError {
10
+ constructor(filePath: string);
11
+ }
12
+ export declare class InvalidFormatError extends XMindError {
13
+ constructor(reason: string);
14
+ }
15
+ export declare class ParseError extends XMindError {
16
+ readonly details?: string | undefined;
17
+ constructor(message: string, details?: string | undefined);
18
+ }
19
+ export declare class SearchError extends XMindError {
20
+ constructor(message: string);
21
+ }
22
+ export declare class BranchExtractionError extends XMindError {
23
+ constructor(message: string);
24
+ }
25
+ export declare function isXMindError(error: unknown): error is XMindError;
26
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,UAAW,SAAQ,KAAK;aACU,IAAI,EAAE,MAAM;gBAA7C,OAAO,EAAE,MAAM,EAAkB,IAAI,GAAE,MAAsB;CAI1E;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,QAAQ,EAAE,MAAM;CAM7B;AAED,qBAAa,kBAAmB,SAAQ,UAAU;gBACpC,MAAM,EAAE,MAAM;CAM3B;AAED,qBAAa,UAAW,SAAQ,UAAU;aACK,OAAO,CAAC,EAAE,MAAM;gBAAjD,OAAO,EAAE,MAAM,EAAkB,OAAO,CAAC,EAAE,MAAM,YAAA;CAM9D;AAED,qBAAa,WAAY,SAAQ,UAAU;gBAC7B,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,qBAAsB,SAAQ,UAAU;gBACvC,OAAO,EAAE,MAAM;CAG5B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Custom error classes for XMind parsing and MCP operations.
3
+ * All errors include actionable messages to guide LLM recovery.
4
+ */
5
+ export class XMindError extends Error {
6
+ constructor(message, code = 'XMIND_ERROR') {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = 'XMindError';
10
+ }
11
+ }
12
+ export class FileNotFoundError extends XMindError {
13
+ constructor(filePath) {
14
+ super(`File not found: ${filePath}. Please provide an absolute path to an existing .xmind file.`, 'FILE_NOT_FOUND');
15
+ }
16
+ }
17
+ export class InvalidFormatError extends XMindError {
18
+ constructor(reason) {
19
+ super(`Invalid .xmind format: ${reason}. The file may be corrupted or created with an unsupported XMind version. Try re-saving in XMind 2020+ (Zen format) or XMind 8.`, 'INVALID_FORMAT');
20
+ }
21
+ }
22
+ export class ParseError extends XMindError {
23
+ constructor(message, details) {
24
+ super(`Failed to parse .xmind: ${message}${details ? ` (${details})` : ''}. Check file integrity or try with a simpler mind map.`, 'PARSE_ERROR');
25
+ this.details = details;
26
+ }
27
+ }
28
+ export class SearchError extends XMindError {
29
+ constructor(message) {
30
+ super(`Search failed: ${message}`, 'SEARCH_ERROR');
31
+ }
32
+ }
33
+ export class BranchExtractionError extends XMindError {
34
+ constructor(message) {
35
+ super(`Failed to extract branch: ${message}`, 'BRANCH_EXTRACTION_ERROR');
36
+ }
37
+ }
38
+ export function isXMindError(error) {
39
+ return error instanceof XMindError;
40
+ }
41
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe,EAAkB,OAAe,aAAa;QACvE,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,SAAI,GAAJ,IAAI,CAAwB;QAEvE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,QAAgB;QAC1B,KAAK,CACH,mBAAmB,QAAQ,+DAA+D,EAC1F,gBAAgB,CACjB,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,UAAU;IAChD,YAAY,MAAc;QACxB,KAAK,CACH,0BAA0B,MAAM,iIAAiI,EACjK,gBAAgB,CACjB,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxC,YAAY,OAAe,EAAkB,OAAgB;QAC3D,KAAK,CACH,2BAA2B,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,wDAAwD,EAC3H,aAAa,CACd,CAAC;QAJyC,YAAO,GAAP,OAAO,CAAS;IAK7D,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,UAAU;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,kBAAkB,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;IACrD,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,UAAU;IACnD,YAAY,OAAe;QACzB,KAAK,CAAC,6BAA6B,OAAO,EAAE,EAAE,yBAAyB,CAAC,CAAC;IAC3E,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,UAAU,CAAC;AACrC,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * File system helpers for .xmind file handling.
3
+ */
4
+ /**
5
+ * Validate that a file path points to an existing, readable .xmind file.
6
+ */
7
+ export declare function validateXMindPath(filePath: string): Promise<void>;
8
+ /**
9
+ * Read entire file into Buffer (suitable for ZIP extraction).
10
+ */
11
+ export declare function readFileBuffer(filePath: string): Promise<Buffer>;
12
+ /**
13
+ * Extract absolute path (resolve ~ and relative paths).
14
+ */
15
+ export declare function resolveFilePath(filePath: string): string;
16
+ /**
17
+ * Estimate token count for markdown output (rough heuristic: 1 token ≈ 4 chars).
18
+ * Helps LLMs decide whether to use search or branch extraction tools.
19
+ */
20
+ export declare function estimateTokenCount(text: string): number;
21
+ /**
22
+ * Check if a string looks like a valid UUID/node ID.
23
+ */
24
+ export declare function isValidNodeId(id: string): boolean;
25
+ //# sourceMappingURL=file-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBvE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjD"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * File system helpers for .xmind file handling.
3
+ */
4
+ import * as fs from 'fs/promises';
5
+ import * as path from 'path';
6
+ import { FileNotFoundError, InvalidFormatError } from './errors.js';
7
+ /**
8
+ * Validate that a file path points to an existing, readable .xmind file.
9
+ */
10
+ export async function validateXMindPath(filePath) {
11
+ try {
12
+ const stat = await fs.stat(filePath);
13
+ if (!stat.isFile()) {
14
+ throw new InvalidFormatError(`${filePath} is not a file`);
15
+ }
16
+ }
17
+ catch (err) {
18
+ if (err.code === 'ENOENT') {
19
+ throw new FileNotFoundError(filePath);
20
+ }
21
+ if (err instanceof InvalidFormatError)
22
+ throw err;
23
+ throw new InvalidFormatError(`Cannot access file: ${err.message}`);
24
+ }
25
+ if (!filePath.endsWith('.xmind')) {
26
+ throw new InvalidFormatError('File must have .xmind extension');
27
+ }
28
+ }
29
+ /**
30
+ * Read entire file into Buffer (suitable for ZIP extraction).
31
+ */
32
+ export async function readFileBuffer(filePath) {
33
+ await validateXMindPath(filePath);
34
+ return fs.readFile(filePath);
35
+ }
36
+ /**
37
+ * Extract absolute path (resolve ~ and relative paths).
38
+ */
39
+ export function resolveFilePath(filePath) {
40
+ if (filePath.startsWith('~')) {
41
+ return path.join(process.env.HOME || '/root', filePath.slice(1));
42
+ }
43
+ return path.resolve(filePath);
44
+ }
45
+ /**
46
+ * Estimate token count for markdown output (rough heuristic: 1 token ≈ 4 chars).
47
+ * Helps LLMs decide whether to use search or branch extraction tools.
48
+ */
49
+ export function estimateTokenCount(text) {
50
+ return Math.ceil(text.length / 4);
51
+ }
52
+ /**
53
+ * Check if a string looks like a valid UUID/node ID.
54
+ */
55
+ export function isValidNodeId(id) {
56
+ return /^[a-zA-Z0-9_-]+$/.test(id);
57
+ }
58
+ //# sourceMappingURL=file-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,MAAM,IAAI,kBAAkB,CAAC,GAAG,QAAQ,gBAAgB,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,GAAG,YAAY,kBAAkB;YAAE,MAAM,GAAG,CAAC;QACjD,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,kBAAkB,CAAC,iCAAiC,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Simple debug logger (optional, can be disabled in production).
3
+ */
4
+ export declare function debug(message: string, data?: unknown): void;
5
+ export declare function info(message: string, data?: unknown): void;
6
+ export declare function warn(message: string, data?: unknown): void;
7
+ export declare function error(message: string, err?: unknown): void;
8
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAI3D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAG1D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAG1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,CAG1D"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Simple debug logger (optional, can be disabled in production).
3
+ */
4
+ const DEBUG = process.env.DEBUG === '1' || process.env.DEBUG === 'true';
5
+ export function debug(message, data) {
6
+ if (!DEBUG)
7
+ return;
8
+ const timestamp = new Date().toISOString();
9
+ console.error(`[${timestamp}] DEBUG:`, message, data ? JSON.stringify(data) : '');
10
+ }
11
+ export function info(message, data) {
12
+ const timestamp = new Date().toISOString();
13
+ console.error(`[${timestamp}] INFO:`, message, data ? JSON.stringify(data) : '');
14
+ }
15
+ export function warn(message, data) {
16
+ const timestamp = new Date().toISOString();
17
+ console.error(`[${timestamp}] WARN:`, message, data ? JSON.stringify(data) : '');
18
+ }
19
+ export function error(message, err) {
20
+ const timestamp = new Date().toISOString();
21
+ console.error(`[${timestamp}] ERROR:`, message, err);
22
+ }
23
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,CAAC;AAExE,MAAM,UAAU,KAAK,CAAC,OAAe,EAAE,IAAc;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAAe,EAAE,IAAc;IAClD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAAe,EAAE,IAAc;IAClD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe,EAAE,GAAa;IAClD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@zengjing/xmind-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for parsing and searching XMind mind maps (.xmind files). Enables Claude and other LLM applications to extract, search, and analyze mind map data.",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "xmind-mcp": "dist/index.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx watch src/index.ts",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch",
19
+ "lint": "tsc --noEmit",
20
+ "parse": "node dist/cli.js",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "keywords": [
32
+ "mcp",
33
+ "xmind",
34
+ "mindmap",
35
+ "mind-map",
36
+ "parser",
37
+ "search",
38
+ "model-context-protocol",
39
+ "claude",
40
+ "ai"
41
+ ],
42
+ "author": "zengjing <hhtczengjing@gmail.com>",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/hhtczengjing/xmind-mcp.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/hhtczengjing/xmind-mcp/issues"
50
+ },
51
+ "homepage": "https://github.com/hhtczengjing/xmind-mcp#readme",
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.0.0",
54
+ "jszip": "^3.10.1",
55
+ "fast-xml-parser": "^4.3.0",
56
+ "zod": "^3.22.0"
57
+ },
58
+ "devDependencies": {
59
+ "typescript": "^5.2.0",
60
+ "@types/node": "^20.0.0",
61
+ "ts-node": "^10.9.0",
62
+ "tsx": "^3.14.0",
63
+ "jest": "^29.7.0",
64
+ "@types/jest": "^29.5.0",
65
+ "ts-jest": "^29.1.0"
66
+ }
67
+ }