@gulibs/safe-coder 0.0.25 → 0.0.27
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 +682 -902
- package/dist/cache/cache-manager.d.ts +71 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +244 -0
- package/dist/cache/cache-manager.js.map +1 -0
- package/dist/executor/cli-executor.d.ts +106 -0
- package/dist/executor/cli-executor.d.ts.map +1 -0
- package/dist/executor/cli-executor.js +133 -0
- package/dist/executor/cli-executor.js.map +1 -0
- package/dist/executor/dependency-checker.d.ts +23 -0
- package/dist/executor/dependency-checker.d.ts.map +1 -0
- package/dist/executor/dependency-checker.js +62 -0
- package/dist/executor/dependency-checker.js.map +1 -0
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/processor/content-processor.d.ts +76 -0
- package/dist/processor/content-processor.d.ts.map +1 -0
- package/dist/processor/content-processor.js +182 -0
- package/dist/processor/content-processor.js.map +1 -0
- package/dist/processor/guide-generator.d.ts +68 -0
- package/dist/processor/guide-generator.d.ts.map +1 -0
- package/dist/processor/guide-generator.js +189 -0
- package/dist/processor/guide-generator.js.map +1 -0
- package/dist/server/safe-coder-mcp.d.ts +18 -0
- package/dist/server/safe-coder-mcp.d.ts.map +1 -0
- package/dist/server/safe-coder-mcp.js +164 -0
- package/dist/server/safe-coder-mcp.js.map +1 -0
- package/dist/tools/cache-tools.d.ts +42 -0
- package/dist/tools/cache-tools.d.ts.map +1 -0
- package/dist/tools/cache-tools.js +70 -0
- package/dist/tools/cache-tools.js.map +1 -0
- package/dist/tools/crawl-documentation.d.ts +57 -0
- package/dist/tools/crawl-documentation.d.ts.map +1 -0
- package/dist/tools/crawl-documentation.js +96 -0
- package/dist/tools/crawl-documentation.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/save-skill.d.ts +49 -0
- package/dist/tools/save-skill.d.ts.map +1 -0
- package/dist/tools/save-skill.js +207 -0
- package/dist/tools/save-skill.js.map +1 -0
- package/package.json +18 -22
- package/dist/documentation/browser-doc-browser.d.ts +0 -41
- package/dist/documentation/browser-doc-browser.d.ts.map +0 -1
- package/dist/documentation/browser-doc-browser.js +0 -357
- package/dist/documentation/browser-doc-browser.js.map +0 -1
- package/dist/documentation/cache.d.ts +0 -13
- package/dist/documentation/cache.d.ts.map +0 -1
- package/dist/documentation/cache.js +0 -48
- package/dist/documentation/cache.js.map +0 -1
- package/dist/documentation/checkpoint-manager.d.ts +0 -38
- package/dist/documentation/checkpoint-manager.d.ts.map +0 -1
- package/dist/documentation/checkpoint-manager.js +0 -101
- package/dist/documentation/checkpoint-manager.js.map +0 -1
- package/dist/documentation/doc-crawler.d.ts +0 -185
- package/dist/documentation/doc-crawler.d.ts.map +0 -1
- package/dist/documentation/doc-crawler.js +0 -1162
- package/dist/documentation/doc-crawler.js.map +0 -1
- package/dist/documentation/github-client.d.ts +0 -13
- package/dist/documentation/github-client.d.ts.map +0 -1
- package/dist/documentation/github-client.js +0 -90
- package/dist/documentation/github-client.js.map +0 -1
- package/dist/documentation/http-fetcher.d.ts +0 -8
- package/dist/documentation/http-fetcher.d.ts.map +0 -1
- package/dist/documentation/http-fetcher.js +0 -31
- package/dist/documentation/http-fetcher.js.map +0 -1
- package/dist/documentation/index.d.ts +0 -16
- package/dist/documentation/index.d.ts.map +0 -1
- package/dist/documentation/index.js +0 -159
- package/dist/documentation/index.js.map +0 -1
- package/dist/documentation/llms-txt/detector.d.ts +0 -31
- package/dist/documentation/llms-txt/detector.d.ts.map +0 -1
- package/dist/documentation/llms-txt/detector.js +0 -77
- package/dist/documentation/llms-txt/detector.js.map +0 -1
- package/dist/documentation/llms-txt/downloader.d.ts +0 -30
- package/dist/documentation/llms-txt/downloader.d.ts.map +0 -1
- package/dist/documentation/llms-txt/downloader.js +0 -84
- package/dist/documentation/llms-txt/downloader.js.map +0 -1
- package/dist/documentation/llms-txt/index.d.ts +0 -4
- package/dist/documentation/llms-txt/index.d.ts.map +0 -1
- package/dist/documentation/llms-txt/index.js +0 -4
- package/dist/documentation/llms-txt/index.js.map +0 -1
- package/dist/documentation/llms-txt/parser.d.ts +0 -43
- package/dist/documentation/llms-txt/parser.d.ts.map +0 -1
- package/dist/documentation/llms-txt/parser.js +0 -177
- package/dist/documentation/llms-txt/parser.js.map +0 -1
- package/dist/documentation/normalizer.d.ts +0 -6
- package/dist/documentation/normalizer.d.ts.map +0 -1
- package/dist/documentation/normalizer.js +0 -38
- package/dist/documentation/normalizer.js.map +0 -1
- package/dist/documentation/npm-client.d.ts +0 -19
- package/dist/documentation/npm-client.d.ts.map +0 -1
- package/dist/documentation/npm-client.js +0 -182
- package/dist/documentation/npm-client.js.map +0 -1
- package/dist/documentation/skill-generator.d.ts +0 -108
- package/dist/documentation/skill-generator.d.ts.map +0 -1
- package/dist/documentation/skill-generator.js +0 -642
- package/dist/documentation/skill-generator.js.map +0 -1
- package/dist/documentation/web-doc-browser.d.ts +0 -67
- package/dist/documentation/web-doc-browser.d.ts.map +0 -1
- package/dist/documentation/web-doc-browser.js +0 -555
- package/dist/documentation/web-doc-browser.js.map +0 -1
- package/dist/errors/api-validator.d.ts +0 -9
- package/dist/errors/api-validator.d.ts.map +0 -1
- package/dist/errors/api-validator.js +0 -57
- package/dist/errors/api-validator.js.map +0 -1
- package/dist/errors/contextual-analysis.d.ts +0 -14
- package/dist/errors/contextual-analysis.d.ts.map +0 -1
- package/dist/errors/contextual-analysis.js +0 -173
- package/dist/errors/contextual-analysis.js.map +0 -1
- package/dist/errors/cross-file-analyzer.d.ts +0 -16
- package/dist/errors/cross-file-analyzer.d.ts.map +0 -1
- package/dist/errors/cross-file-analyzer.js +0 -172
- package/dist/errors/cross-file-analyzer.js.map +0 -1
- package/dist/errors/eslint-integration.d.ts +0 -9
- package/dist/errors/eslint-integration.d.ts.map +0 -1
- package/dist/errors/eslint-integration.js +0 -131
- package/dist/errors/eslint-integration.js.map +0 -1
- package/dist/errors/framework-detector.d.ts +0 -10
- package/dist/errors/framework-detector.d.ts.map +0 -1
- package/dist/errors/framework-detector.js +0 -126
- package/dist/errors/framework-detector.js.map +0 -1
- package/dist/errors/index.d.ts +0 -18
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js +0 -134
- package/dist/errors/index.js.map +0 -1
- package/dist/errors/pattern-matcher.d.ts +0 -25
- package/dist/errors/pattern-matcher.d.ts.map +0 -1
- package/dist/errors/pattern-matcher.js +0 -44
- package/dist/errors/pattern-matcher.js.map +0 -1
- package/dist/errors/patterns.d.ts +0 -11
- package/dist/errors/patterns.d.ts.map +0 -1
- package/dist/errors/patterns.js +0 -351
- package/dist/errors/patterns.js.map +0 -1
- package/dist/errors/performance-detector.d.ts +0 -11
- package/dist/errors/performance-detector.d.ts.map +0 -1
- package/dist/errors/performance-detector.js +0 -119
- package/dist/errors/performance-detector.js.map +0 -1
- package/dist/errors/runtime-detector.d.ts +0 -7
- package/dist/errors/runtime-detector.d.ts.map +0 -1
- package/dist/errors/runtime-detector.js +0 -86
- package/dist/errors/runtime-detector.js.map +0 -1
- package/dist/errors/security-detector.d.ts +0 -6
- package/dist/errors/security-detector.d.ts.map +0 -1
- package/dist/errors/security-detector.js +0 -75
- package/dist/errors/security-detector.js.map +0 -1
- package/dist/errors/typescript-integration.d.ts +0 -6
- package/dist/errors/typescript-integration.d.ts.map +0 -1
- package/dist/errors/typescript-integration.js +0 -46
- package/dist/errors/typescript-integration.js.map +0 -1
- package/dist/server/mcp-server.d.ts +0 -14
- package/dist/server/mcp-server.d.ts.map +0 -1
- package/dist/server/mcp-server.js +0 -776
- package/dist/server/mcp-server.js.map +0 -1
- package/dist/types/documentation.d.ts +0 -26
- package/dist/types/documentation.d.ts.map +0 -1
- package/dist/types/documentation.js +0 -2
- package/dist/types/documentation.js.map +0 -1
- package/dist/utils/config.d.ts +0 -21
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -34
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/http-client.d.ts +0 -17
- package/dist/utils/http-client.d.ts.map +0 -1
- package/dist/utils/http-client.js +0 -62
- package/dist/utils/http-client.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -36
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -128
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/rate-limiter.d.ts +0 -9
- package/dist/utils/rate-limiter.d.ts.map +0 -1
- package/dist/utils/rate-limiter.js +0 -26
- package/dist/utils/rate-limiter.js.map +0 -1
- package/dist/validation/auto-fix.d.ts +0 -15
- package/dist/validation/auto-fix.d.ts.map +0 -1
- package/dist/validation/auto-fix.js +0 -49
- package/dist/validation/auto-fix.js.map +0 -1
- package/dist/validation/index.d.ts +0 -21
- package/dist/validation/index.d.ts.map +0 -1
- package/dist/validation/index.js +0 -45
- package/dist/validation/index.js.map +0 -1
- package/dist/validation/resolution-db.d.ts +0 -15
- package/dist/validation/resolution-db.d.ts.map +0 -1
- package/dist/validation/resolution-db.js +0 -62
- package/dist/validation/resolution-db.js.map +0 -1
|
@@ -1,776 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { readFileSync } from 'fs';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
import { dirname, join } from 'path';
|
|
7
|
-
import { DocumentationService } from '../documentation/index.js';
|
|
8
|
-
import { WebDocumentationBrowser } from '../documentation/web-doc-browser.js';
|
|
9
|
-
import { DocumentationCrawler } from '../documentation/doc-crawler.js';
|
|
10
|
-
import { SkillGenerator } from '../documentation/skill-generator.js';
|
|
11
|
-
import { ErrorDetectionService } from '../errors/index.js';
|
|
12
|
-
import { CodeValidationService } from '../validation/index.js';
|
|
13
|
-
import { loadConfig } from '../utils/config.js';
|
|
14
|
-
import { logger } from '../utils/logger.js';
|
|
15
|
-
import { HttpClient } from '../utils/http-client.js';
|
|
16
|
-
export class SafeCoderMCPServer {
|
|
17
|
-
server;
|
|
18
|
-
documentationService;
|
|
19
|
-
webDocBrowser;
|
|
20
|
-
docCrawler;
|
|
21
|
-
skillGenerator;
|
|
22
|
-
errorDetection;
|
|
23
|
-
validation;
|
|
24
|
-
constructor() {
|
|
25
|
-
this.server = new McpServer({
|
|
26
|
-
name: 'safe-coder',
|
|
27
|
-
version: '1.0.0',
|
|
28
|
-
});
|
|
29
|
-
// Initialize services with configuration
|
|
30
|
-
const config = loadConfig();
|
|
31
|
-
this.documentationService = new DocumentationService(config.cacheTTLMinutes, config.githubToken, config.proxy);
|
|
32
|
-
this.webDocBrowser = new WebDocumentationBrowser(config.proxy ? new HttpClient(config.proxy) : undefined);
|
|
33
|
-
this.docCrawler = new DocumentationCrawler(config.proxy ? new HttpClient(config.proxy) : undefined);
|
|
34
|
-
this.skillGenerator = new SkillGenerator();
|
|
35
|
-
this.errorDetection = new ErrorDetectionService(config.errorDetection?.enableRuntime ?? true, config.errorDetection?.enablePerformance ?? true, config.errorDetection?.enableSecurity ?? true, config.errorDetection?.enableFramework ?? true, config.errorDetection?.enableApiValidation ?? true, config.errorDetection?.enableCrossFile ?? false);
|
|
36
|
-
this.validation = new CodeValidationService();
|
|
37
|
-
this.setupTools();
|
|
38
|
-
this.setupResources();
|
|
39
|
-
}
|
|
40
|
-
setupTools() {
|
|
41
|
-
// Register get_documentation tool
|
|
42
|
-
this.server.registerTool('get_documentation', {
|
|
43
|
-
title: 'Get Documentation',
|
|
44
|
-
description: 'Fetch up-to-date documentation (README, API docs, usage examples) for JavaScript/TypeScript libraries and packages. MANDATORY: ALWAYS call this tool immediately when user requests documentation, README, or mentions a package/library name. Trigger phrases (English): "get", "fetch", "read", "show", "documentation", "README", "docs", "how to use [library]", "I want to use [library]". Trigger phrases (Chinese): "获取", "读取", "查看", "显示", "文档", "README", "说明", "使用 [库名]", "获取 [库名] 的文档", "读取 [库名] 的 README". Extract package name from user request (e.g., "@gulibs/tegg-sequelize" from "获取@gulibs/tegg-sequelize最新README内容" → packageName: "@gulibs/tegg-sequelize"). Supports npm packages (including scoped packages like @gulibs/tegg-sequelize), GitHub repositories (owner/repo), and HTTP documentation sources. DO NOT create or edit files - ONLY fetch documentation using this tool.',
|
|
45
|
-
inputSchema: {
|
|
46
|
-
packageName: z.string(),
|
|
47
|
-
version: z.string().optional(),
|
|
48
|
-
source: z.enum(['npm', 'github', 'http']).optional(),
|
|
49
|
-
forceRefresh: z.boolean().optional().describe('Force refresh even if cached (default: false)'),
|
|
50
|
-
},
|
|
51
|
-
outputSchema: {
|
|
52
|
-
name: z.string(),
|
|
53
|
-
version: z.string().optional(),
|
|
54
|
-
content: z.string(),
|
|
55
|
-
source: z.enum(['npm', 'github', 'http']),
|
|
56
|
-
url: z.string().optional(),
|
|
57
|
-
lastUpdated: z.string(),
|
|
58
|
-
},
|
|
59
|
-
}, async (args) => {
|
|
60
|
-
const requestId = logger.generateRequestId();
|
|
61
|
-
const startTime = Date.now();
|
|
62
|
-
const { packageName, version, source, forceRefresh = false } = args;
|
|
63
|
-
logger.toolInvoked('get_documentation', requestId, { packageName, version, source, forceRefresh });
|
|
64
|
-
try {
|
|
65
|
-
const doc = await this.documentationService.getDocumentation(packageName, version, source, forceRefresh);
|
|
66
|
-
const duration = Date.now() - startTime;
|
|
67
|
-
logger.toolCompleted('get_documentation', requestId, duration, true);
|
|
68
|
-
return {
|
|
69
|
-
content: [
|
|
70
|
-
{
|
|
71
|
-
type: 'text',
|
|
72
|
-
text: JSON.stringify(doc, null, 2),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
structuredContent: {
|
|
76
|
-
name: doc.name,
|
|
77
|
-
version: doc.version,
|
|
78
|
-
content: doc.content,
|
|
79
|
-
source: doc.source,
|
|
80
|
-
url: doc.url,
|
|
81
|
-
lastUpdated: doc.lastUpdated.toISOString(),
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
const duration = Date.now() - startTime;
|
|
87
|
-
logger.toolError('get_documentation', requestId, error instanceof Error ? error : new Error(String(error)), { packageName, version, source });
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
// Register detect_errors tool
|
|
92
|
-
this.server.registerTool('detect_errors', {
|
|
93
|
-
title: 'Detect Errors',
|
|
94
|
-
description: 'Detect errors, warnings, and potential issues in JavaScript/TypeScript code using multiple detection methods (ESLint, TypeScript, pattern matching, security, performance). Use this when the user asks to check code for errors, find bugs, analyze code quality, detect security vulnerabilities, or identify performance issues. Keywords: "detect errors", "check code", "find bugs", "analyze", "what\'s wrong".',
|
|
95
|
-
inputSchema: {
|
|
96
|
-
code: z.string(),
|
|
97
|
-
filename: z.string().optional(),
|
|
98
|
-
},
|
|
99
|
-
outputSchema: {
|
|
100
|
-
errors: z.array(z.object({
|
|
101
|
-
type: z.string(),
|
|
102
|
-
message: z.string(),
|
|
103
|
-
line: z.number().optional(),
|
|
104
|
-
column: z.number().optional(),
|
|
105
|
-
priority: z.number(),
|
|
106
|
-
fix: z.string().optional(),
|
|
107
|
-
pattern: z.string().optional(), // Regex pattern that matched (from pattern-matcher)
|
|
108
|
-
})),
|
|
109
|
-
count: z.number(),
|
|
110
|
-
},
|
|
111
|
-
}, async (args) => {
|
|
112
|
-
const requestId = logger.generateRequestId();
|
|
113
|
-
const startTime = Date.now();
|
|
114
|
-
const { code, filename = 'code.ts' } = args;
|
|
115
|
-
logger.toolInvoked('detect_errors', requestId, { filename, codeLength: code.length });
|
|
116
|
-
try {
|
|
117
|
-
const errors = await this.errorDetection.detectErrors(code, filename);
|
|
118
|
-
const duration = Date.now() - startTime;
|
|
119
|
-
logger.toolCompleted('detect_errors', requestId, duration, true);
|
|
120
|
-
return {
|
|
121
|
-
content: [
|
|
122
|
-
{
|
|
123
|
-
type: 'text',
|
|
124
|
-
text: JSON.stringify({ errors, count: errors.length }, null, 2),
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
structuredContent: {
|
|
128
|
-
errors,
|
|
129
|
-
count: errors.length,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
const duration = Date.now() - startTime;
|
|
135
|
-
logger.toolError('detect_errors', requestId, error instanceof Error ? error : new Error(String(error)), { filename });
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
// Register validate_code tool
|
|
140
|
-
this.server.registerTool('validate_code', {
|
|
141
|
-
title: 'Validate Code',
|
|
142
|
-
description: 'Validate JavaScript/TypeScript code and automatically fix common errors including syntax errors, missing imports, type errors, and code quality issues. Use this when the user asks to validate, fix, or clean up code. Keywords: "validate code", "fix errors", "auto-fix", "clean code", "repair".',
|
|
143
|
-
inputSchema: {
|
|
144
|
-
code: z.string(),
|
|
145
|
-
filename: z.string().optional(),
|
|
146
|
-
autoFix: z.boolean().optional(),
|
|
147
|
-
},
|
|
148
|
-
outputSchema: {
|
|
149
|
-
valid: z.boolean(),
|
|
150
|
-
errors: z.array(z.any()),
|
|
151
|
-
warnings: z.array(z.any()),
|
|
152
|
-
fixedCode: z.string().optional(),
|
|
153
|
-
fixes: z.array(z.object({
|
|
154
|
-
error: z.string(),
|
|
155
|
-
fix: z.string(),
|
|
156
|
-
})).optional(),
|
|
157
|
-
},
|
|
158
|
-
}, async (args) => {
|
|
159
|
-
const requestId = logger.generateRequestId();
|
|
160
|
-
const startTime = Date.now();
|
|
161
|
-
const { code, filename = 'code.ts', autoFix = true } = args;
|
|
162
|
-
logger.toolInvoked('validate_code', requestId, { filename, autoFix, codeLength: code.length });
|
|
163
|
-
try {
|
|
164
|
-
const result = await this.validation.validateCode(code, filename, autoFix ?? true);
|
|
165
|
-
const duration = Date.now() - startTime;
|
|
166
|
-
logger.toolCompleted('validate_code', requestId, duration, true);
|
|
167
|
-
return {
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: 'text',
|
|
171
|
-
text: JSON.stringify(result, null, 2),
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
structuredContent: result,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
const duration = Date.now() - startTime;
|
|
179
|
-
logger.toolError('validate_code', requestId, error instanceof Error ? error : new Error(String(error)), { filename });
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
// Register get_status tool
|
|
184
|
-
this.server.registerTool('get_status', {
|
|
185
|
-
title: 'Get Server Status',
|
|
186
|
-
description: 'Get current status and statistics of the safe-coder MCP server including uptime, available tools, cache size, and process information. Use this when the user asks about server status, service health, or available tools. Keywords: "status", "is it running", "server info", "available tools".',
|
|
187
|
-
inputSchema: {},
|
|
188
|
-
outputSchema: {
|
|
189
|
-
status: z.enum(['running', 'error']),
|
|
190
|
-
uptime: z.number(),
|
|
191
|
-
version: z.string(),
|
|
192
|
-
tools: z.array(z.string()),
|
|
193
|
-
cacheSize: z.number(),
|
|
194
|
-
pid: z.number(),
|
|
195
|
-
},
|
|
196
|
-
}, async () => {
|
|
197
|
-
const requestId = logger.generateRequestId();
|
|
198
|
-
logger.toolInvoked('get_status', requestId);
|
|
199
|
-
try {
|
|
200
|
-
// Read version from package.json
|
|
201
|
-
let version = 'unknown';
|
|
202
|
-
try {
|
|
203
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
204
|
-
const __dirname = dirname(__filename);
|
|
205
|
-
const packageJsonPath = join(__dirname, '../../package.json');
|
|
206
|
-
const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
|
|
207
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
208
|
-
version = packageJson.version || 'unknown';
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
logger.warn('Failed to read version from package.json', { error: error instanceof Error ? error.message : String(error) });
|
|
212
|
-
version = 'unknown';
|
|
213
|
-
}
|
|
214
|
-
const cache = this.documentationService.getCache();
|
|
215
|
-
const status = {
|
|
216
|
-
status: 'running',
|
|
217
|
-
uptime: process.uptime(),
|
|
218
|
-
version,
|
|
219
|
-
tools: ['get_documentation', 'browse_documentation', 'crawl_documentation', 'detect_errors', 'validate_code', 'resolve_error', 'get_status', 'get_version', 'detect_spa'],
|
|
220
|
-
cacheSize: cache.size(),
|
|
221
|
-
pid: process.pid,
|
|
222
|
-
};
|
|
223
|
-
logger.toolCompleted('get_status', requestId, 0, true);
|
|
224
|
-
return {
|
|
225
|
-
content: [
|
|
226
|
-
{
|
|
227
|
-
type: 'text',
|
|
228
|
-
text: JSON.stringify(status, null, 2),
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
structuredContent: status,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
logger.toolError('get_status', requestId, error instanceof Error ? error : new Error(String(error)));
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
// Register get_version tool
|
|
240
|
-
this.server.registerTool('get_version', {
|
|
241
|
-
title: 'Get Version',
|
|
242
|
-
description: 'Get the current version number of the safe-coder MCP server. Use this when the user wants to check the version, verify if an update was successful, or needs version information. Keywords: "version", "what version", "check version", "get version number".',
|
|
243
|
-
inputSchema: {},
|
|
244
|
-
outputSchema: {
|
|
245
|
-
version: z.string(),
|
|
246
|
-
name: z.string(),
|
|
247
|
-
description: z.string().optional(),
|
|
248
|
-
},
|
|
249
|
-
}, async () => {
|
|
250
|
-
const requestId = logger.generateRequestId();
|
|
251
|
-
logger.toolInvoked('get_version', requestId);
|
|
252
|
-
try {
|
|
253
|
-
// Read version from package.json
|
|
254
|
-
let version = 'unknown';
|
|
255
|
-
let name = 'safe-coder';
|
|
256
|
-
let description;
|
|
257
|
-
try {
|
|
258
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
259
|
-
const __dirname = dirname(__filename);
|
|
260
|
-
const packageJsonPath = join(__dirname, '../../package.json');
|
|
261
|
-
const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
|
|
262
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
263
|
-
version = packageJson.version || 'unknown';
|
|
264
|
-
name = packageJson.name || 'safe-coder';
|
|
265
|
-
description = packageJson.description;
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
logger.warn('Failed to read version from package.json', { error: error instanceof Error ? error.message : String(error) });
|
|
269
|
-
version = 'unknown';
|
|
270
|
-
}
|
|
271
|
-
const result = {
|
|
272
|
-
version,
|
|
273
|
-
name,
|
|
274
|
-
description,
|
|
275
|
-
};
|
|
276
|
-
logger.toolCompleted('get_version', requestId, 0, true);
|
|
277
|
-
return {
|
|
278
|
-
content: [
|
|
279
|
-
{
|
|
280
|
-
type: 'text',
|
|
281
|
-
text: JSON.stringify(result, null, 2),
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
structuredContent: result,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
logger.toolError('get_version', requestId, error instanceof Error ? error : new Error(String(error)));
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
// Register resolve_error tool
|
|
293
|
-
this.server.registerTool('resolve_error', {
|
|
294
|
-
title: 'Resolve Error',
|
|
295
|
-
description: 'Get detailed resolution suggestions and fixes for specific coding errors. Use this when the user encounters an error and needs help understanding how to fix it, or asks for solutions to a specific error message. Keywords: "how to fix", "resolve error", "error solution", "fix this error".',
|
|
296
|
-
inputSchema: {
|
|
297
|
-
errorType: z.string(),
|
|
298
|
-
message: z.string(),
|
|
299
|
-
line: z.number().optional(),
|
|
300
|
-
},
|
|
301
|
-
outputSchema: {
|
|
302
|
-
errorType: z.string(),
|
|
303
|
-
description: z.string(),
|
|
304
|
-
solution: z.string(),
|
|
305
|
-
codeExample: z.string().optional(),
|
|
306
|
-
},
|
|
307
|
-
}, async (args) => {
|
|
308
|
-
const requestId = logger.generateRequestId();
|
|
309
|
-
const startTime = Date.now();
|
|
310
|
-
const { errorType, message, line } = args;
|
|
311
|
-
logger.toolInvoked('resolve_error', requestId, { errorType, message, line });
|
|
312
|
-
try {
|
|
313
|
-
const error = {
|
|
314
|
-
type: errorType,
|
|
315
|
-
message,
|
|
316
|
-
line,
|
|
317
|
-
priority: 3,
|
|
318
|
-
};
|
|
319
|
-
const resolution = this.validation.resolveError(error);
|
|
320
|
-
const duration = Date.now() - startTime;
|
|
321
|
-
logger.toolCompleted('resolve_error', requestId, duration, true);
|
|
322
|
-
return {
|
|
323
|
-
content: [
|
|
324
|
-
{
|
|
325
|
-
type: 'text',
|
|
326
|
-
text: JSON.stringify(resolution, null, 2),
|
|
327
|
-
},
|
|
328
|
-
],
|
|
329
|
-
structuredContent: resolution,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
const duration = Date.now() - startTime;
|
|
334
|
-
logger.toolError('resolve_error', requestId, error instanceof Error ? error : new Error(String(error)), { errorType });
|
|
335
|
-
throw error;
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
// Register browse_documentation tool
|
|
339
|
-
this.server.registerTool('browse_documentation', {
|
|
340
|
-
title: 'Browse Web Documentation',
|
|
341
|
-
description: 'Browse and search web documentation pages. Use this when the user provides a documentation URL and wants to view specific content, navigate to different pages, or search within documentation. The tool automatically checks if the URL is a documentation page and only processes documentation sites to save tokens. Supports query-based content filtering and navigation links extraction. Trigger phrases (English): "browse docs", "view documentation page", "navigate to", "show me the docs at", "documentation URL". Trigger phrases (Chinese): "浏览文档", "查看文档页面", "导航到", "显示文档", "文档地址".',
|
|
342
|
-
inputSchema: {
|
|
343
|
-
url: z.string().describe('The documentation URL to browse'),
|
|
344
|
-
query: z.string().optional().describe('Optional search query to filter content (e.g., "authentication", "API reference")'),
|
|
345
|
-
navigateTo: z.string().optional().describe('Optional relative or absolute URL to navigate to within the documentation'),
|
|
346
|
-
},
|
|
347
|
-
outputSchema: {
|
|
348
|
-
url: z.string(),
|
|
349
|
-
title: z.string(),
|
|
350
|
-
content: z.string(),
|
|
351
|
-
isDocumentation: z.boolean(),
|
|
352
|
-
navigationLinks: z.array(z.object({
|
|
353
|
-
text: z.string(),
|
|
354
|
-
url: z.string(),
|
|
355
|
-
isInternal: z.boolean(),
|
|
356
|
-
})),
|
|
357
|
-
sections: z.array(z.object({
|
|
358
|
-
title: z.string(),
|
|
359
|
-
content: z.string(),
|
|
360
|
-
anchor: z.string().optional(),
|
|
361
|
-
})),
|
|
362
|
-
},
|
|
363
|
-
}, async (args) => {
|
|
364
|
-
const requestId = logger.generateRequestId();
|
|
365
|
-
const startTime = Date.now();
|
|
366
|
-
const { url, query, navigateTo } = args;
|
|
367
|
-
logger.toolInvoked('browse_documentation', requestId, { url, query, navigateTo });
|
|
368
|
-
try {
|
|
369
|
-
let page;
|
|
370
|
-
if (navigateTo) {
|
|
371
|
-
page = await this.webDocBrowser.navigateTo(navigateTo, url);
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
page = await this.webDocBrowser.browsePage(url, query);
|
|
375
|
-
}
|
|
376
|
-
const duration = Date.now() - startTime;
|
|
377
|
-
logger.toolCompleted('browse_documentation', requestId, duration, true);
|
|
378
|
-
return {
|
|
379
|
-
content: [
|
|
380
|
-
{
|
|
381
|
-
type: 'text',
|
|
382
|
-
text: JSON.stringify({
|
|
383
|
-
url: page.url,
|
|
384
|
-
title: page.title,
|
|
385
|
-
content: page.content,
|
|
386
|
-
navigationLinks: page.navigationLinks.slice(0, 20), // Limit for display
|
|
387
|
-
sections: page.sections,
|
|
388
|
-
}, null, 2),
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
structuredContent: {
|
|
392
|
-
url: page.url,
|
|
393
|
-
title: page.title,
|
|
394
|
-
content: page.content,
|
|
395
|
-
isDocumentation: page.isDocumentation,
|
|
396
|
-
navigationLinks: page.navigationLinks,
|
|
397
|
-
sections: page.sections,
|
|
398
|
-
},
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
catch (error) {
|
|
402
|
-
const duration = Date.now() - startTime;
|
|
403
|
-
logger.toolError('browse_documentation', requestId, error instanceof Error ? error : new Error(String(error)), { url, query });
|
|
404
|
-
throw error;
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
// Register crawl_documentation tool
|
|
408
|
-
this.server.registerTool('crawl_documentation', {
|
|
409
|
-
title: 'Crawl Documentation and Generate Agent Skill',
|
|
410
|
-
description: 'Recursively crawl websites starting from a root URL using HTTP client (axios). Can crawl any website with extractable content, not just documentation. Supports BFS/DFS strategies, parallel crawling, checkpoint/resume, llms.txt detection, and Markdown files. Follows links within the same domain, extracts content from multiple pages, and generates structured agent skill output in Claude skill format (SKILL.md with references/ directory). For SPA sites that require JavaScript rendering, set useBrowserAutomation=true to leverage Cursor/Claude built-in browser tools. The tool automatically detects content boundaries, follows links within the same domain, and generates Markdown-formatted skill output. Returns abandon flag if content is insufficient for skill generation. Trigger phrases (English): "crawl website", "generate skill from", "scrape site", "create agent skill from", "crawl from", "recursive crawl". Trigger phrases (Chinese): "爬取网站", "生成技能", "抓取网站", "从网站生成技能", "递归抓取", "嵌套爬取".',
|
|
411
|
-
inputSchema: {
|
|
412
|
-
url: z.string().describe('Root URL to start crawling from (any website with extractable content)'),
|
|
413
|
-
crawlStrategy: z.enum(['bfs', 'dfs']).optional().describe('Crawl strategy: bfs (breadth-first, layer by layer) or dfs (depth-first, follow one path deep). Default: bfs'),
|
|
414
|
-
maxDepth: z.number().optional().describe('Maximum crawl depth (default: 3)'),
|
|
415
|
-
maxPages: z.number().optional().describe('Maximum number of pages to crawl (default: 50)'),
|
|
416
|
-
includePaths: z.array(z.string()).optional().describe('Additional path patterns to include in crawl'),
|
|
417
|
-
excludePaths: z.array(z.string()).optional().describe('Path patterns to exclude from crawl'),
|
|
418
|
-
rateLimit: z.number().optional().describe('Delay in milliseconds between requests (default: 500)'),
|
|
419
|
-
maxRetries: z.number().optional().describe('Maximum number of retries for failed requests (default: 2)'),
|
|
420
|
-
retryDelay: z.number().optional().describe('Delay in milliseconds before retry (default: 1000)'),
|
|
421
|
-
useBrowserAutomation: z.boolean().optional().describe('Use agent\'s browser tools (browser_navigate, browser_snapshot, browser_eval) for SPA sites that require JavaScript rendering (default: false). When enabled, the agent will use its built-in browser automation capabilities.'),
|
|
422
|
-
skipLlmsTxt: z.boolean().optional().describe('Skip automatic detection and use of llms.txt files (default: false). When false, the crawler will automatically detect and use llms.txt for optimized content discovery.'),
|
|
423
|
-
workers: z.number().optional().describe('Number of parallel workers for faster crawling (default: 1, max: 10). Use higher values for better performance on large documentation sites.'),
|
|
424
|
-
checkpoint: z.object({
|
|
425
|
-
enabled: z.boolean().describe('Enable checkpoint/resume functionality'),
|
|
426
|
-
interval: z.number().optional().describe('Save checkpoint every N pages (default: 10)'),
|
|
427
|
-
file: z.string().optional().describe('Custom checkpoint file path'),
|
|
428
|
-
}).optional().describe('Checkpoint configuration for resuming interrupted crawls'),
|
|
429
|
-
resume: z.boolean().optional().describe('Resume from last checkpoint if available (requires checkpoint.enabled=true)'),
|
|
430
|
-
outputDir: z.string().optional().describe('Directory to save skill files (default: no file saved, only returns content)'),
|
|
431
|
-
filename: z.string().optional().describe('Custom filename for skill file (without extension)'),
|
|
432
|
-
},
|
|
433
|
-
outputSchema: {
|
|
434
|
-
skillMd: z.string(),
|
|
435
|
-
metadata: z.object({
|
|
436
|
-
title: z.string(),
|
|
437
|
-
description: z.string(),
|
|
438
|
-
sourceUrl: z.string(),
|
|
439
|
-
pagesCrawled: z.number(),
|
|
440
|
-
maxDepthReached: z.number(),
|
|
441
|
-
generatedAt: z.string(),
|
|
442
|
-
sourceUrls: z.array(z.string()),
|
|
443
|
-
}),
|
|
444
|
-
crawlStats: z.object({
|
|
445
|
-
totalPages: z.number(),
|
|
446
|
-
maxDepthReached: z.number(),
|
|
447
|
-
errors: z.array(z.object({
|
|
448
|
-
url: z.string(),
|
|
449
|
-
error: z.string(),
|
|
450
|
-
})),
|
|
451
|
-
abandoned: z.boolean().optional(),
|
|
452
|
-
abandonReason: z.enum(['insufficient_content', 'media_only', 'empty_pages', 'no_structured_content']).optional(),
|
|
453
|
-
linkDiscoveryStats: z.object({
|
|
454
|
-
totalLinksFound: z.number(),
|
|
455
|
-
linksFiltered: z.object({
|
|
456
|
-
notContent: z.number(), // Renamed from notDocumentation
|
|
457
|
-
externalDomain: z.number(),
|
|
458
|
-
alreadyVisited: z.number(),
|
|
459
|
-
excludedPattern: z.number(),
|
|
460
|
-
depthLimit: z.number(),
|
|
461
|
-
}),
|
|
462
|
-
linksQueued: z.number(),
|
|
463
|
-
pagesDiscovered: z.number(),
|
|
464
|
-
pagesCrawled: z.number(),
|
|
465
|
-
}),
|
|
466
|
-
}),
|
|
467
|
-
files: z.object({
|
|
468
|
-
skillDir: z.string().optional(),
|
|
469
|
-
skillMdPath: z.string().optional(),
|
|
470
|
-
referenceFiles: z.array(z.string()).optional(),
|
|
471
|
-
}).optional(),
|
|
472
|
-
},
|
|
473
|
-
}, async (args) => {
|
|
474
|
-
const requestId = logger.generateRequestId();
|
|
475
|
-
const startTime = Date.now();
|
|
476
|
-
const { url, crawlStrategy, maxDepth, maxPages, includePaths, excludePaths, rateLimit, maxRetries, retryDelay, useBrowserAutomation, skipLlmsTxt, workers, checkpoint, resume, outputDir, filename } = args;
|
|
477
|
-
logger.toolInvoked('crawl_documentation', requestId, {
|
|
478
|
-
url,
|
|
479
|
-
crawlStrategy,
|
|
480
|
-
maxDepth,
|
|
481
|
-
maxPages,
|
|
482
|
-
maxRetries,
|
|
483
|
-
workers,
|
|
484
|
-
checkpoint: checkpoint?.enabled,
|
|
485
|
-
resume,
|
|
486
|
-
outputDir
|
|
487
|
-
});
|
|
488
|
-
try {
|
|
489
|
-
// Crawl website (permissive - any website with extractable content)
|
|
490
|
-
// For SPA sites, use browser automation if requested
|
|
491
|
-
let browserAutomationNeeded = false;
|
|
492
|
-
let spaDetectionResult = null;
|
|
493
|
-
if (useBrowserAutomation) {
|
|
494
|
-
logger.info('Browser automation requested - checking if SPA detection is needed', { url });
|
|
495
|
-
// Detect SPA first to determine if browser automation is needed
|
|
496
|
-
try {
|
|
497
|
-
const spaDetection = await this.webDocBrowser.detectSPA(url);
|
|
498
|
-
spaDetectionResult = {
|
|
499
|
-
isSPA: spaDetection.isSPA,
|
|
500
|
-
confidence: spaDetection.confidence,
|
|
501
|
-
suggestion: spaDetection.suggestion,
|
|
502
|
-
};
|
|
503
|
-
if (spaDetection.isSPA && spaDetection.confidence !== 'low') {
|
|
504
|
-
browserAutomationNeeded = true;
|
|
505
|
-
logger.info('SPA detected - browser automation is required', {
|
|
506
|
-
url,
|
|
507
|
-
confidence: spaDetection.confidence,
|
|
508
|
-
indicators: spaDetection.indicators,
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
catch (error) {
|
|
513
|
-
logger.debug('SPA detection failed, will proceed with HTTP-only crawling', {
|
|
514
|
-
url,
|
|
515
|
-
error: error instanceof Error ? error.message : String(error),
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const crawlResult = await this.docCrawler.crawl(url, {
|
|
520
|
-
crawlStrategy,
|
|
521
|
-
maxDepth,
|
|
522
|
-
maxPages,
|
|
523
|
-
includePaths,
|
|
524
|
-
excludePaths,
|
|
525
|
-
rateLimit,
|
|
526
|
-
maxRetries,
|
|
527
|
-
retryDelay,
|
|
528
|
-
useBrowserAutomation: useBrowserAutomation || false,
|
|
529
|
-
skipLlmsTxt,
|
|
530
|
-
workers,
|
|
531
|
-
checkpoint: checkpoint ? {
|
|
532
|
-
enabled: checkpoint.enabled,
|
|
533
|
-
interval: checkpoint.interval || 10,
|
|
534
|
-
file: checkpoint.file,
|
|
535
|
-
} : undefined,
|
|
536
|
-
resume,
|
|
537
|
-
});
|
|
538
|
-
// If browser automation was requested and SPA detected, provide clear guidance
|
|
539
|
-
if (useBrowserAutomation && browserAutomationNeeded) {
|
|
540
|
-
logger.warn('Browser automation is required for this SPA site', {
|
|
541
|
-
url,
|
|
542
|
-
message: 'The agent needs to use browser tools (browser_navigate, browser_snapshot, browser_eval) to render JavaScript content before crawling',
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
// Check if content was abandoned (insufficient for skill generation)
|
|
546
|
-
if (crawlResult.abandoned) {
|
|
547
|
-
logger.warn('Crawl completed but content is insufficient for skill generation', {
|
|
548
|
-
reason: crawlResult.abandonReason,
|
|
549
|
-
pagesCrawled: crawlResult.totalPages,
|
|
550
|
-
});
|
|
551
|
-
// If browser automation was requested but not used, suggest it
|
|
552
|
-
let suggestion = `Consider crawling more pages or a different website.`;
|
|
553
|
-
if (useBrowserAutomation && browserAutomationNeeded && spaDetectionResult?.isSPA) {
|
|
554
|
-
suggestion = `This appears to be a SPA site. Please use browser tools (browser_navigate, browser_snapshot, browser_eval) to render the page content first, then try crawling again. Steps: 1) Use browser_navigate to load the page, 2) Use browser_snapshot to get rendered HTML, 3) Use browser_eval to click navigation elements and discover more content, 4) Then use crawl_documentation with the rendered content.`;
|
|
555
|
-
}
|
|
556
|
-
return {
|
|
557
|
-
content: [
|
|
558
|
-
{
|
|
559
|
-
type: 'text',
|
|
560
|
-
text: JSON.stringify({
|
|
561
|
-
abandoned: true,
|
|
562
|
-
abandonReason: crawlResult.abandonReason,
|
|
563
|
-
message: `Cannot generate skill: ${crawlResult.abandonReason}. Pages crawled: ${crawlResult.totalPages}, but content is insufficient. ${suggestion}`,
|
|
564
|
-
browserAutomationNeeded: browserAutomationNeeded || false,
|
|
565
|
-
spaDetected: spaDetectionResult?.isSPA || false,
|
|
566
|
-
crawlStats: {
|
|
567
|
-
totalPages: crawlResult.totalPages,
|
|
568
|
-
maxDepthReached: crawlResult.maxDepthReached,
|
|
569
|
-
errors: crawlResult.errors,
|
|
570
|
-
linkDiscoveryStats: crawlResult.linkDiscoveryStats,
|
|
571
|
-
},
|
|
572
|
-
}, null, 2),
|
|
573
|
-
},
|
|
574
|
-
],
|
|
575
|
-
structuredContent: {
|
|
576
|
-
abandoned: true,
|
|
577
|
-
abandonReason: crawlResult.abandonReason,
|
|
578
|
-
message: `Cannot generate skill: ${crawlResult.abandonReason}. Pages crawled: ${crawlResult.totalPages}, but content is insufficient. ${suggestion}`,
|
|
579
|
-
browserAutomationNeeded: browserAutomationNeeded || false,
|
|
580
|
-
spaDetected: spaDetectionResult?.isSPA || false,
|
|
581
|
-
crawlStats: {
|
|
582
|
-
totalPages: crawlResult.totalPages,
|
|
583
|
-
maxDepthReached: crawlResult.maxDepthReached,
|
|
584
|
-
errors: crawlResult.errors,
|
|
585
|
-
linkDiscoveryStats: crawlResult.linkDiscoveryStats,
|
|
586
|
-
},
|
|
587
|
-
},
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
// Generate skill (pass filename for skill name generation)
|
|
591
|
-
const skill = this.skillGenerator.generateSkill(crawlResult, url, filename);
|
|
592
|
-
// Save to file if outputDir is provided
|
|
593
|
-
let files;
|
|
594
|
-
let saveError;
|
|
595
|
-
if (outputDir) {
|
|
596
|
-
try {
|
|
597
|
-
const savedFiles = await this.skillGenerator.saveSkill(skill, outputDir, filename);
|
|
598
|
-
files = {
|
|
599
|
-
skillDir: savedFiles.skillDir,
|
|
600
|
-
skillMdPath: savedFiles.skillMdPath,
|
|
601
|
-
referenceFiles: savedFiles.referenceFiles,
|
|
602
|
-
};
|
|
603
|
-
logger.info(`Skill saved to directory: ${savedFiles.skillDir}`);
|
|
604
|
-
logger.info(` SKILL.md: ${savedFiles.skillMdPath}`);
|
|
605
|
-
logger.info(` Reference files: ${savedFiles.referenceFiles.length}`);
|
|
606
|
-
}
|
|
607
|
-
catch (err) {
|
|
608
|
-
saveError = err instanceof Error ? err.message : String(err);
|
|
609
|
-
logger.error(`Failed to save skill files: ${saveError}`, {
|
|
610
|
-
outputDir,
|
|
611
|
-
filename,
|
|
612
|
-
error: err instanceof Error ? err.stack : String(err),
|
|
613
|
-
});
|
|
614
|
-
// Continue even if file save fails, but include error in response
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
const duration = Date.now() - startTime;
|
|
618
|
-
logger.toolCompleted('crawl_documentation', requestId, duration, true);
|
|
619
|
-
// Add browser automation guidance if needed
|
|
620
|
-
const responseMessage = [];
|
|
621
|
-
if (useBrowserAutomation && browserAutomationNeeded) {
|
|
622
|
-
responseMessage.push(`\n⚠️ Browser Automation Required:\n` +
|
|
623
|
-
`This site is a SPA (Single Page Application) that requires JavaScript rendering.\n` +
|
|
624
|
-
`To properly crawl this site, you need to:\n` +
|
|
625
|
-
`1. Use browser_navigate to load the page: ${url}\n` +
|
|
626
|
-
`2. Use browser_snapshot to get the fully rendered HTML after JavaScript executes\n` +
|
|
627
|
-
`3. Use browser_eval to interact with navigation elements (click menus, expand sections)\n` +
|
|
628
|
-
`4. Extract links from the rendered DOM\n` +
|
|
629
|
-
`5. Repeat for discovered links\n\n` +
|
|
630
|
-
`The current crawl used HTTP-only mode, which may have missed JavaScript-rendered content.\n`);
|
|
631
|
-
}
|
|
632
|
-
return {
|
|
633
|
-
content: [
|
|
634
|
-
{
|
|
635
|
-
type: 'text',
|
|
636
|
-
text: (responseMessage.join('') + JSON.stringify({
|
|
637
|
-
skillMd: skill.skillMd,
|
|
638
|
-
metadata: skill.metadata,
|
|
639
|
-
crawlStats: {
|
|
640
|
-
totalPages: crawlResult.totalPages,
|
|
641
|
-
maxDepthReached: crawlResult.maxDepthReached,
|
|
642
|
-
errors: crawlResult.errors,
|
|
643
|
-
linkDiscoveryStats: crawlResult.linkDiscoveryStats,
|
|
644
|
-
abandoned: crawlResult.abandoned || false,
|
|
645
|
-
abandonReason: crawlResult.abandonReason,
|
|
646
|
-
},
|
|
647
|
-
browserAutomationNeeded: browserAutomationNeeded || false,
|
|
648
|
-
spaDetected: spaDetectionResult?.isSPA || false,
|
|
649
|
-
files,
|
|
650
|
-
saveError,
|
|
651
|
-
}, null, 2)),
|
|
652
|
-
},
|
|
653
|
-
],
|
|
654
|
-
structuredContent: {
|
|
655
|
-
skillMd: skill.skillMd,
|
|
656
|
-
metadata: skill.metadata,
|
|
657
|
-
crawlStats: {
|
|
658
|
-
totalPages: crawlResult.totalPages,
|
|
659
|
-
maxDepthReached: crawlResult.maxDepthReached,
|
|
660
|
-
errors: crawlResult.errors,
|
|
661
|
-
linkDiscoveryStats: crawlResult.linkDiscoveryStats,
|
|
662
|
-
abandoned: crawlResult.abandoned || false,
|
|
663
|
-
abandonReason: crawlResult.abandonReason,
|
|
664
|
-
},
|
|
665
|
-
browserAutomationNeeded: browserAutomationNeeded || false,
|
|
666
|
-
spaDetected: spaDetectionResult?.isSPA || false,
|
|
667
|
-
browserAutomationGuidance: useBrowserAutomation && browserAutomationNeeded ? {
|
|
668
|
-
steps: [
|
|
669
|
-
'Use browser_navigate to load the page',
|
|
670
|
-
'Use browser_snapshot to get rendered HTML after JavaScript executes',
|
|
671
|
-
'Use browser_eval to interact with navigation elements',
|
|
672
|
-
'Extract links from rendered DOM',
|
|
673
|
-
'Repeat for discovered links',
|
|
674
|
-
],
|
|
675
|
-
note: 'HTTP-only crawling may miss JavaScript-rendered content for SPA sites',
|
|
676
|
-
} : undefined,
|
|
677
|
-
files,
|
|
678
|
-
saveError,
|
|
679
|
-
},
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
catch (error) {
|
|
683
|
-
const duration = Date.now() - startTime;
|
|
684
|
-
logger.toolError('crawl_documentation', requestId, error instanceof Error ? error : new Error(String(error)), { url });
|
|
685
|
-
throw error;
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
// Register detect_spa tool
|
|
689
|
-
this.server.registerTool('detect_spa', {
|
|
690
|
-
title: 'Detect Single Page Application',
|
|
691
|
-
description: 'Detect if a website is a Single Page Application (SPA) that requires JavaScript rendering. Use this to check if a documentation site needs browser automation before crawling. Keywords: "detect SPA", "check if SPA", "is this a SPA", "needs JavaScript rendering".',
|
|
692
|
-
inputSchema: {
|
|
693
|
-
url: z.string().describe('The URL to check for SPA characteristics'),
|
|
694
|
-
},
|
|
695
|
-
outputSchema: {
|
|
696
|
-
isSPA: z.boolean(),
|
|
697
|
-
confidence: z.enum(['high', 'medium', 'low']),
|
|
698
|
-
indicators: z.array(z.string()),
|
|
699
|
-
suggestion: z.string().optional(),
|
|
700
|
-
},
|
|
701
|
-
}, async (args) => {
|
|
702
|
-
const requestId = logger.generateRequestId();
|
|
703
|
-
const startTime = Date.now();
|
|
704
|
-
const { url } = args;
|
|
705
|
-
logger.toolInvoked('detect_spa', requestId, { url });
|
|
706
|
-
try {
|
|
707
|
-
const spaDetection = await this.webDocBrowser.detectSPA(url);
|
|
708
|
-
const duration = Date.now() - startTime;
|
|
709
|
-
logger.toolCompleted('detect_spa', requestId, duration, true);
|
|
710
|
-
return {
|
|
711
|
-
content: [
|
|
712
|
-
{
|
|
713
|
-
type: 'text',
|
|
714
|
-
text: JSON.stringify(spaDetection, null, 2),
|
|
715
|
-
},
|
|
716
|
-
],
|
|
717
|
-
structuredContent: spaDetection,
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
catch (error) {
|
|
721
|
-
const duration = Date.now() - startTime;
|
|
722
|
-
logger.toolError('detect_spa', requestId, error instanceof Error ? error : new Error(String(error)), { url });
|
|
723
|
-
throw error;
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
setupResources() {
|
|
728
|
-
// Register documentation resource
|
|
729
|
-
this.server.registerResource('documentation', 'safe-coder://documentation', {
|
|
730
|
-
title: 'Cached Documentation',
|
|
731
|
-
description: 'Access cached documentation entries',
|
|
732
|
-
mimeType: 'application/json',
|
|
733
|
-
}, async () => {
|
|
734
|
-
const cache = this.documentationService.getCache();
|
|
735
|
-
return {
|
|
736
|
-
contents: [
|
|
737
|
-
{
|
|
738
|
-
uri: 'safe-coder://documentation',
|
|
739
|
-
mimeType: 'application/json',
|
|
740
|
-
text: JSON.stringify({
|
|
741
|
-
cacheSize: cache.size(),
|
|
742
|
-
description: 'Cached documentation entries',
|
|
743
|
-
}, null, 2),
|
|
744
|
-
},
|
|
745
|
-
],
|
|
746
|
-
};
|
|
747
|
-
});
|
|
748
|
-
// Register error patterns resource
|
|
749
|
-
this.server.registerResource('error-patterns', 'safe-coder://error-patterns', {
|
|
750
|
-
title: 'Error Patterns',
|
|
751
|
-
description: 'Database of error patterns and resolutions',
|
|
752
|
-
mimeType: 'application/json',
|
|
753
|
-
}, async () => {
|
|
754
|
-
return {
|
|
755
|
-
contents: [
|
|
756
|
-
{
|
|
757
|
-
uri: 'safe-coder://error-patterns',
|
|
758
|
-
mimeType: 'application/json',
|
|
759
|
-
text: JSON.stringify({
|
|
760
|
-
description: 'Error patterns database',
|
|
761
|
-
patterns: 'See error detection service',
|
|
762
|
-
}, null, 2),
|
|
763
|
-
},
|
|
764
|
-
],
|
|
765
|
-
};
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
async run() {
|
|
769
|
-
logger.serverStarted();
|
|
770
|
-
const transport = new StdioServerTransport();
|
|
771
|
-
await this.server.connect(transport);
|
|
772
|
-
logger.serverConnected();
|
|
773
|
-
logger.info('Safe Coder MCP server running on stdio');
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
//# sourceMappingURL=mcp-server.js.map
|