@afterxleep/doc-bot 1.9.0 → 1.10.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.
- package/README.md +30 -0
- package/bin/doc-bot.js +5 -0
- package/package.json +7 -2
- package/src/__tests__/docset-integration.test.js +146 -0
- package/src/index.js +245 -0
- package/src/services/docset/__tests__/DocsetDatabase.test.js +337 -0
- package/src/services/docset/__tests__/DocsetService.test.js +195 -0
- package/src/services/docset/database.js +203 -0
- package/src/services/docset/index.js +349 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ doc-bot is an intelligent documentation server that:
|
|
|
13
13
|
- 🧠 **Auto-indexes** content for smart inference, based on metadata and keywords
|
|
14
14
|
- 🤖 **Provides agentic tools** to query, and update your documentation
|
|
15
15
|
- 🔄 **Updates** automatically when docs change
|
|
16
|
+
- 📚 **Supports Docsets** for searching official API documentation alongside your custom docs
|
|
16
17
|
|
|
17
18
|
## Why MCP Instead of Static Rules?
|
|
18
19
|
|
|
@@ -112,6 +113,18 @@ IDE's use static rule files (like Cursor Rules or Copilot's .github/copilot-inst
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
```
|
|
116
|
+
|
|
117
|
+
**With Docsets support (for official API documentation):**
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"doc-bot": {
|
|
122
|
+
"command": "npx",
|
|
123
|
+
"args": ["@afterxleep/doc-bot@latest", "--docs", "./docs", "--docsets", "~/MyDocSets"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
115
128
|
Note: If a relative path does not work, you can use VSCode `${workspaceFolder}`environment variable `${workspaceFolder}/my-custom-docs`
|
|
116
129
|
|
|
117
130
|
|
|
@@ -386,6 +399,23 @@ git push origin main
|
|
|
386
399
|
git push --tags # Push version tags
|
|
387
400
|
```
|
|
388
401
|
|
|
402
|
+
## Docset Support
|
|
403
|
+
|
|
404
|
+
doc-bot now supports [Docsets](https://kapeli.com/docsets) - pre-indexed documentation used by Dash, Zeal, and Velocity. This allows you to search official API documentation alongside your custom project docs.
|
|
405
|
+
|
|
406
|
+
### Key Features
|
|
407
|
+
- Search iOS, macOS, Swift, and other official documentation
|
|
408
|
+
- Install docsets from URLs or local files
|
|
409
|
+
- Unified search across both custom docs and official APIs
|
|
410
|
+
- No manual HTML parsing required
|
|
411
|
+
|
|
412
|
+
### Quick Start
|
|
413
|
+
1. **Install a docset**: Use the `add_docset` tool with a URL or local path
|
|
414
|
+
2. **Search documentation**: Use `search_docsets` for docsets only, or `search_all` for unified results
|
|
415
|
+
3. **Manage docsets**: List installed docsets with `list_docsets`, remove with `remove_docset`
|
|
416
|
+
|
|
417
|
+
See [DOCSETS.md](./DOCSETS.md) for detailed documentation.
|
|
418
|
+
|
|
389
419
|
## License
|
|
390
420
|
|
|
391
421
|
MIT License - see the [LICENSE](LICENSE) file for details.
|
package/bin/doc-bot.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { program } from 'commander';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
5
6
|
import fs from 'fs-extra';
|
|
6
7
|
import { DocsServer } from '../src/index.js';
|
|
7
8
|
import { readFileSync } from 'fs';
|
|
@@ -17,6 +18,7 @@ program
|
|
|
17
18
|
.description('Generic MCP server for intelligent documentation access')
|
|
18
19
|
.version(packageJson.version)
|
|
19
20
|
.option('-d, --docs <path>', 'Path to docs folder', 'doc-bot')
|
|
21
|
+
.option('--docsets <path>', 'Path to docsets folder', path.join(os.homedir(), 'Developer', 'DocSets'))
|
|
20
22
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
21
23
|
.option('-w, --watch', 'Watch for file changes')
|
|
22
24
|
.parse();
|
|
@@ -25,6 +27,7 @@ const options = program.opts();
|
|
|
25
27
|
|
|
26
28
|
async function main() {
|
|
27
29
|
const docsPath = path.resolve(options.docs);
|
|
30
|
+
const docsetsPath = path.resolve(options.docsets);
|
|
28
31
|
|
|
29
32
|
// Check if documentation folder exists
|
|
30
33
|
if (!await fs.pathExists(docsPath)) {
|
|
@@ -46,6 +49,7 @@ async function main() {
|
|
|
46
49
|
|
|
47
50
|
const server = new DocsServer({
|
|
48
51
|
docsPath,
|
|
52
|
+
docsetsPath,
|
|
49
53
|
verbose: options.verbose,
|
|
50
54
|
watch: options.watch
|
|
51
55
|
});
|
|
@@ -53,6 +57,7 @@ async function main() {
|
|
|
53
57
|
if (options.verbose) {
|
|
54
58
|
console.error('🚀 Starting doc-bot...');
|
|
55
59
|
console.error(`📁 Documentation: ${docsPath}`);
|
|
60
|
+
console.error(`📚 Docsets: ${docsetsPath}`);
|
|
56
61
|
console.error(`⚙️ Configuration: Frontmatter-based`);
|
|
57
62
|
|
|
58
63
|
if (options.watch) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afterxleep/doc-bot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Generic MCP server for intelligent documentation access in any project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -45,7 +45,12 @@
|
|
|
45
45
|
"chokidar": "^3.5.3",
|
|
46
46
|
"fs-extra": "^11.0.0",
|
|
47
47
|
"glob": "^10.3.0",
|
|
48
|
-
"yaml": "^2.3.0"
|
|
48
|
+
"yaml": "^2.3.0",
|
|
49
|
+
"better-sqlite3": "^11.2.1",
|
|
50
|
+
"axios": "^1.6.5",
|
|
51
|
+
"tar": "^6.2.0",
|
|
52
|
+
"plist": "^3.1.0",
|
|
53
|
+
"adm-zip": "^0.5.10"
|
|
49
54
|
},
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"eslint": "^8.57.0",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { DocsServer } from '../index.js';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
describe('DocsServer Docset Integration', () => {
|
|
12
|
+
let server;
|
|
13
|
+
let tempDocsPath;
|
|
14
|
+
let tempDocsetsPath;
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
// Create temporary directories
|
|
18
|
+
tempDocsPath = path.join(__dirname, 'temp-docs-' + Date.now());
|
|
19
|
+
tempDocsetsPath = path.join(__dirname, 'temp-docsets-' + Date.now());
|
|
20
|
+
|
|
21
|
+
await fs.ensureDir(tempDocsPath);
|
|
22
|
+
await fs.ensureDir(tempDocsetsPath);
|
|
23
|
+
|
|
24
|
+
// Create a simple test doc
|
|
25
|
+
await fs.writeFile(
|
|
26
|
+
path.join(tempDocsPath, 'test.md'),
|
|
27
|
+
'---\nalwaysApply: true\ntitle: Test Doc\n---\n# Test'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
if (server) {
|
|
33
|
+
await server.stop();
|
|
34
|
+
}
|
|
35
|
+
await fs.remove(tempDocsPath);
|
|
36
|
+
await fs.remove(tempDocsetsPath);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Server initialization with docsets', () => {
|
|
40
|
+
it('should initialize with custom docsets path', async () => {
|
|
41
|
+
server = new DocsServer({
|
|
42
|
+
docsPath: tempDocsPath,
|
|
43
|
+
docsetsPath: tempDocsetsPath,
|
|
44
|
+
verbose: false
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await server.start();
|
|
48
|
+
|
|
49
|
+
expect(server.docsetService).toBeDefined();
|
|
50
|
+
expect(server.docsetService.storagePath).toBe(tempDocsetsPath);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should use default docsets path when not provided', async () => {
|
|
54
|
+
server = new DocsServer({
|
|
55
|
+
docsPath: tempDocsPath,
|
|
56
|
+
verbose: false
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await server.start();
|
|
60
|
+
|
|
61
|
+
const expectedPath = path.join(os.homedir(), 'Developer', 'DocSets');
|
|
62
|
+
expect(server.docsetService.storagePath).toBe(expectedPath);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('Docset service functionality', () => {
|
|
67
|
+
beforeEach(async () => {
|
|
68
|
+
server = new DocsServer({
|
|
69
|
+
docsPath: tempDocsPath,
|
|
70
|
+
docsetsPath: tempDocsetsPath,
|
|
71
|
+
verbose: false
|
|
72
|
+
});
|
|
73
|
+
await server.start();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should have docset service initialized', () => {
|
|
77
|
+
expect(server.docsetService).toBeDefined();
|
|
78
|
+
expect(server.docsetDatabase).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should have empty docsets initially', async () => {
|
|
82
|
+
const docsets = await server.docsetService.listDocsets();
|
|
83
|
+
expect(docsets).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle docset operations', async () => {
|
|
87
|
+
// Create a mock docset
|
|
88
|
+
const mockDocsetPath = path.join(tempDocsetsPath, 'Mock.docset');
|
|
89
|
+
const contentsPath = path.join(mockDocsetPath, 'Contents');
|
|
90
|
+
const resourcesPath = path.join(contentsPath, 'Resources');
|
|
91
|
+
|
|
92
|
+
await fs.ensureDir(resourcesPath);
|
|
93
|
+
|
|
94
|
+
// Create Info.plist
|
|
95
|
+
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
96
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
97
|
+
<plist version="1.0">
|
|
98
|
+
<dict>
|
|
99
|
+
<key>CFBundleName</key>
|
|
100
|
+
<string>Mock Documentation</string>
|
|
101
|
+
<key>CFBundleIdentifier</key>
|
|
102
|
+
<string>mock.documentation</string>
|
|
103
|
+
</dict>
|
|
104
|
+
</plist>`;
|
|
105
|
+
await fs.writeFile(path.join(contentsPath, 'Info.plist'), infoPlist);
|
|
106
|
+
|
|
107
|
+
// Create SQLite database
|
|
108
|
+
const Database = (await import('better-sqlite3')).default;
|
|
109
|
+
const dbPath = path.join(resourcesPath, 'docSet.dsidx');
|
|
110
|
+
const db = new Database(dbPath);
|
|
111
|
+
db.exec(`
|
|
112
|
+
CREATE TABLE searchIndex(
|
|
113
|
+
id INTEGER PRIMARY KEY,
|
|
114
|
+
name TEXT,
|
|
115
|
+
type TEXT,
|
|
116
|
+
path TEXT
|
|
117
|
+
);
|
|
118
|
+
`);
|
|
119
|
+
db.prepare('INSERT INTO searchIndex (name, type, path) VALUES (?, ?, ?)')
|
|
120
|
+
.run('TestClass', 'Class', 'test.html');
|
|
121
|
+
db.close();
|
|
122
|
+
|
|
123
|
+
// Test adding docset
|
|
124
|
+
const docsetInfo = await server.docsetService.addDocset(mockDocsetPath);
|
|
125
|
+
expect(docsetInfo.name).toBe('Mock Documentation');
|
|
126
|
+
|
|
127
|
+
// Test listing docsets
|
|
128
|
+
const docsets = await server.docsetService.listDocsets();
|
|
129
|
+
expect(docsets).toHaveLength(1);
|
|
130
|
+
expect(docsets[0].name).toBe('Mock Documentation');
|
|
131
|
+
|
|
132
|
+
// Add to database for searching
|
|
133
|
+
server.docsetDatabase.addDocset(docsetInfo);
|
|
134
|
+
|
|
135
|
+
// Test searching
|
|
136
|
+
const searchResults = server.docsetDatabase.search('Test');
|
|
137
|
+
expect(searchResults).toHaveLength(1);
|
|
138
|
+
expect(searchResults[0].name).toBe('TestClass');
|
|
139
|
+
|
|
140
|
+
// Test removing docset
|
|
141
|
+
await server.docsetService.removeDocset(docsetInfo.id);
|
|
142
|
+
const finalDocsets = await server.docsetService.listDocsets();
|
|
143
|
+
expect(finalDocsets).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
package/src/index.js
CHANGED
|
@@ -3,8 +3,11 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { DocumentationService } from './services/DocumentationService.js';
|
|
5
5
|
import { InferenceEngine } from './services/InferenceEngine.js';
|
|
6
|
+
import { DocsetService } from './services/docset/index.js';
|
|
7
|
+
import { MultiDocsetDatabase } from './services/docset/database.js';
|
|
6
8
|
import chokidar from 'chokidar';
|
|
7
9
|
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
8
11
|
import { promises as fs } from 'fs';
|
|
9
12
|
import { fileURLToPath } from 'url';
|
|
10
13
|
import { dirname } from 'path';
|
|
@@ -16,6 +19,7 @@ class DocsServer {
|
|
|
16
19
|
constructor(options = {}) {
|
|
17
20
|
this.options = {
|
|
18
21
|
docsPath: options.docsPath || './doc-bot',
|
|
22
|
+
docsetsPath: options.docsetsPath || path.join(os.homedir(), 'Developer', 'DocSets'),
|
|
19
23
|
verbose: options.verbose || false,
|
|
20
24
|
watch: options.watch || false,
|
|
21
25
|
...options
|
|
@@ -38,6 +42,10 @@ class DocsServer {
|
|
|
38
42
|
this.docService = new DocumentationService(this.options.docsPath);
|
|
39
43
|
this.inferenceEngine = new InferenceEngine(this.docService);
|
|
40
44
|
|
|
45
|
+
// Initialize docset services
|
|
46
|
+
this.docsetService = new DocsetService(this.options.docsetsPath);
|
|
47
|
+
this.docsetDatabase = new MultiDocsetDatabase();
|
|
48
|
+
|
|
41
49
|
this.setupHandlers();
|
|
42
50
|
|
|
43
51
|
if (this.options.watch) {
|
|
@@ -252,6 +260,95 @@ class DocsServer {
|
|
|
252
260
|
properties: {},
|
|
253
261
|
additionalProperties: false
|
|
254
262
|
}
|
|
263
|
+
},
|
|
264
|
+
// Docset tools
|
|
265
|
+
{
|
|
266
|
+
name: 'add_docset',
|
|
267
|
+
description: 'Add a new documentation set from a URL or local file path. Supports .docset directories, .tgz/.tar.gz/.zip archives.',
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
source: {
|
|
272
|
+
type: 'string',
|
|
273
|
+
description: 'URL to download docset from OR local file path. Example: https://example.com/iOS.tgz or /path/to/Swift.docset'
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
required: ['source']
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'remove_docset',
|
|
281
|
+
description: 'Remove an installed documentation set',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
docsetId: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'ID of the docset to remove'
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
required: ['docsetId']
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'list_docsets',
|
|
295
|
+
description: 'List all installed documentation sets',
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'search_docsets',
|
|
303
|
+
description: 'Search installed documentation sets for API references, classes, methods, and guides. Returns official documentation with direct file links.',
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: {
|
|
307
|
+
query: {
|
|
308
|
+
type: 'string',
|
|
309
|
+
description: 'Search query - can be class names, method names, concepts, or any technical term'
|
|
310
|
+
},
|
|
311
|
+
type: {
|
|
312
|
+
type: 'string',
|
|
313
|
+
description: 'Optional: filter by type (Class, Method, Function, Guide, Property, Protocol, Enum, etc.)',
|
|
314
|
+
enum: ['Class', 'Method', 'Function', 'Property', 'Protocol', 'Enum', 'Structure', 'Guide', 'Sample', 'Category', 'Constant', 'Variable', 'Typedef', 'Macro']
|
|
315
|
+
},
|
|
316
|
+
docsetId: {
|
|
317
|
+
type: 'string',
|
|
318
|
+
description: 'Optional: limit search to specific docset ID'
|
|
319
|
+
},
|
|
320
|
+
limit: {
|
|
321
|
+
type: 'number',
|
|
322
|
+
description: 'Maximum results to return (default: 50)',
|
|
323
|
+
minimum: 1,
|
|
324
|
+
maximum: 200,
|
|
325
|
+
default: 50
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
required: ['query']
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'docset_stats',
|
|
333
|
+
description: 'Get detailed statistics about installed documentation sets',
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: 'object',
|
|
336
|
+
properties: {}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'search_all',
|
|
341
|
+
description: 'Search both Markdown documentation and installed docsets. Provides unified results from all sources.',
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
query: {
|
|
346
|
+
type: 'string',
|
|
347
|
+
description: 'Search query to find in both documentation types'
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
required: ['query']
|
|
351
|
+
}
|
|
255
352
|
}
|
|
256
353
|
]
|
|
257
354
|
};
|
|
@@ -368,6 +465,134 @@ class DocsServer {
|
|
|
368
465
|
}]
|
|
369
466
|
};
|
|
370
467
|
|
|
468
|
+
// Docset tools
|
|
469
|
+
case 'add_docset':
|
|
470
|
+
const { source } = args || {};
|
|
471
|
+
if (!source) {
|
|
472
|
+
throw new Error('source parameter is required');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
const docsetInfo = await this.docsetService.addDocset(source);
|
|
477
|
+
this.docsetDatabase.addDocset(docsetInfo);
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
content: [{
|
|
481
|
+
type: 'text',
|
|
482
|
+
text: JSON.stringify({
|
|
483
|
+
success: true,
|
|
484
|
+
docset: docsetInfo,
|
|
485
|
+
message: `Successfully added docset: ${docsetInfo.name}`
|
|
486
|
+
}, null, 2)
|
|
487
|
+
}]
|
|
488
|
+
};
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return {
|
|
491
|
+
content: [{
|
|
492
|
+
type: 'text',
|
|
493
|
+
text: JSON.stringify({
|
|
494
|
+
success: false,
|
|
495
|
+
error: error.message
|
|
496
|
+
}, null, 2)
|
|
497
|
+
}]
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
case 'remove_docset':
|
|
502
|
+
const { docsetId } = args || {};
|
|
503
|
+
if (!docsetId) {
|
|
504
|
+
throw new Error('docsetId parameter is required');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
await this.docsetService.removeDocset(docsetId);
|
|
509
|
+
this.docsetDatabase.removeDocset(docsetId);
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
content: [{
|
|
513
|
+
type: 'text',
|
|
514
|
+
text: JSON.stringify({
|
|
515
|
+
success: true,
|
|
516
|
+
message: `Successfully removed docset: ${docsetId}`
|
|
517
|
+
}, null, 2)
|
|
518
|
+
}]
|
|
519
|
+
};
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return {
|
|
522
|
+
content: [{
|
|
523
|
+
type: 'text',
|
|
524
|
+
text: JSON.stringify({
|
|
525
|
+
success: false,
|
|
526
|
+
error: error.message
|
|
527
|
+
}, null, 2)
|
|
528
|
+
}]
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case 'list_docsets':
|
|
533
|
+
const docsets = await this.docsetService.listDocsets();
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
content: [{
|
|
537
|
+
type: 'text',
|
|
538
|
+
text: JSON.stringify(docsets, null, 2)
|
|
539
|
+
}]
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
case 'search_docsets':
|
|
543
|
+
const { query: docsetQuery, type, docsetId: searchDocsetId, limit } = args || {};
|
|
544
|
+
if (!docsetQuery) {
|
|
545
|
+
throw new Error('query parameter is required');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const docsetResults = this.docsetDatabase.search(docsetQuery, { type, docsetId: searchDocsetId, limit });
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
content: [{
|
|
552
|
+
type: 'text',
|
|
553
|
+
text: JSON.stringify({
|
|
554
|
+
query: docsetQuery,
|
|
555
|
+
resultCount: docsetResults.length,
|
|
556
|
+
results: docsetResults
|
|
557
|
+
}, null, 2)
|
|
558
|
+
}]
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
case 'docset_stats':
|
|
562
|
+
const stats = this.docsetDatabase.getStats();
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
content: [{
|
|
566
|
+
type: 'text',
|
|
567
|
+
text: JSON.stringify(stats, null, 2)
|
|
568
|
+
}]
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
case 'search_all':
|
|
572
|
+
const { query: allQuery } = args || {};
|
|
573
|
+
if (!allQuery) {
|
|
574
|
+
throw new Error('query parameter is required');
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Search markdown documentation
|
|
578
|
+
const markdownResults = await this.docService.searchDocuments(allQuery);
|
|
579
|
+
|
|
580
|
+
// Search docsets
|
|
581
|
+
const allDocsetResults = this.docsetDatabase.search(allQuery, { limit: 50 });
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
content: [{
|
|
585
|
+
type: 'text',
|
|
586
|
+
text: JSON.stringify({
|
|
587
|
+
query: allQuery,
|
|
588
|
+
sources: ['markdown', 'docsets'],
|
|
589
|
+
markdownResults: markdownResults.slice(0, 10),
|
|
590
|
+
docsetResults: allDocsetResults,
|
|
591
|
+
totalResults: markdownResults.length + allDocsetResults.length
|
|
592
|
+
}, null, 2)
|
|
593
|
+
}]
|
|
594
|
+
};
|
|
595
|
+
|
|
371
596
|
default:
|
|
372
597
|
throw new Error(`Unknown tool: ${name}`);
|
|
373
598
|
}
|
|
@@ -839,6 +1064,19 @@ class DocsServer {
|
|
|
839
1064
|
await this.docService.initialize();
|
|
840
1065
|
await this.inferenceEngine.initialize();
|
|
841
1066
|
|
|
1067
|
+
// Initialize docset service
|
|
1068
|
+
await this.docsetService.initialize();
|
|
1069
|
+
|
|
1070
|
+
// Load existing docsets into the database
|
|
1071
|
+
const existingDocsets = await this.docsetService.listDocsets();
|
|
1072
|
+
for (const docset of existingDocsets) {
|
|
1073
|
+
this.docsetDatabase.addDocset(docset);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (this.options.verbose && existingDocsets.length > 0) {
|
|
1077
|
+
console.error(`📚 Loaded ${existingDocsets.length} docsets from ${this.docsetService.storagePath}`);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
842
1080
|
// Start server
|
|
843
1081
|
const transport = new StdioServerTransport();
|
|
844
1082
|
await this.server.connect(transport);
|
|
@@ -848,6 +1086,13 @@ class DocsServer {
|
|
|
848
1086
|
console.error('🚀 Using frontmatter-based configuration');
|
|
849
1087
|
}
|
|
850
1088
|
}
|
|
1089
|
+
|
|
1090
|
+
async stop() {
|
|
1091
|
+
if (this.docsetDatabase) {
|
|
1092
|
+
this.docsetDatabase.closeAll();
|
|
1093
|
+
}
|
|
1094
|
+
await this.server.close();
|
|
1095
|
+
}
|
|
851
1096
|
}
|
|
852
1097
|
|
|
853
1098
|
export { DocsServer };
|