mcp-researchpowerpack 3.6.9
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 +635 -0
- package/dist/clients/reddit.d.ts +74 -0
- package/dist/clients/reddit.d.ts.map +1 -0
- package/dist/clients/reddit.js +305 -0
- package/dist/clients/reddit.js.map +1 -0
- package/dist/clients/research.d.ts +67 -0
- package/dist/clients/research.d.ts.map +1 -0
- package/dist/clients/research.js +252 -0
- package/dist/clients/research.js.map +1 -0
- package/dist/clients/scraper.d.ts +71 -0
- package/dist/clients/scraper.d.ts.map +1 -0
- package/dist/clients/scraper.js +321 -0
- package/dist/clients/scraper.js.map +1 -0
- package/dist/clients/search.d.ts +62 -0
- package/dist/clients/search.d.ts.map +1 -0
- package/dist/clients/search.js +219 -0
- package/dist/clients/search.js.map +1 -0
- package/dist/config/index.d.ts +62 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +142 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +305 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +81 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/yaml/tools.yaml +130 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/deep-research.d.ts +64 -0
- package/dist/schemas/deep-research.d.ts.map +1 -0
- package/dist/schemas/deep-research.js +224 -0
- package/dist/schemas/deep-research.js.map +1 -0
- package/dist/schemas/scrape-links.d.ts +32 -0
- package/dist/schemas/scrape-links.d.ts.map +1 -0
- package/dist/schemas/scrape-links.js +34 -0
- package/dist/schemas/scrape-links.js.map +1 -0
- package/dist/schemas/web-search.d.ts +22 -0
- package/dist/schemas/web-search.d.ts.map +1 -0
- package/dist/schemas/web-search.js +21 -0
- package/dist/schemas/web-search.js.map +1 -0
- package/dist/services/file-attachment.d.ts +30 -0
- package/dist/services/file-attachment.d.ts.map +1 -0
- package/dist/services/file-attachment.js +199 -0
- package/dist/services/file-attachment.js.map +1 -0
- package/dist/services/llm-processor.d.ts +27 -0
- package/dist/services/llm-processor.d.ts.map +1 -0
- package/dist/services/llm-processor.js +179 -0
- package/dist/services/llm-processor.js.map +1 -0
- package/dist/services/markdown-cleaner.d.ts +8 -0
- package/dist/services/markdown-cleaner.d.ts.map +1 -0
- package/dist/services/markdown-cleaner.js +44 -0
- package/dist/services/markdown-cleaner.js.map +1 -0
- package/dist/tools/definitions.d.ts +16 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +17 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/reddit.d.ts +14 -0
- package/dist/tools/reddit.d.ts.map +1 -0
- package/dist/tools/reddit.js +213 -0
- package/dist/tools/reddit.js.map +1 -0
- package/dist/tools/registry.d.ts +71 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +242 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/research.d.ts +14 -0
- package/dist/tools/research.d.ts.map +1 -0
- package/dist/tools/research.js +194 -0
- package/dist/tools/research.js.map +1 -0
- package/dist/tools/scrape.d.ts +14 -0
- package/dist/tools/scrape.d.ts.map +1 -0
- package/dist/tools/scrape.js +201 -0
- package/dist/tools/scrape.js.map +1 -0
- package/dist/tools/search.d.ts +10 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +137 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/utils.d.ts +105 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +159 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/utils/concurrency.d.ts +29 -0
- package/dist/utils/concurrency.d.ts.map +1 -0
- package/dist/utils/concurrency.js +73 -0
- package/dist/utils/concurrency.js.map +1 -0
- package/dist/utils/errors.d.ts +77 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +335 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +39 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +57 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/markdown-formatter.d.ts +5 -0
- package/dist/utils/markdown-formatter.d.ts.map +1 -0
- package/dist/utils/markdown-formatter.js +15 -0
- package/dist/utils/markdown-formatter.js.map +1 -0
- package/dist/utils/response.d.ts +88 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +151 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/url-aggregator.d.ts +90 -0
- package/dist/utils/url-aggregator.d.ts.map +1 -0
- package/dist/utils/url-aggregator.js +502 -0
- package/dist/utils/url-aggregator.js.map +1 -0
- package/dist/version.d.ts +30 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +60 -0
- package/dist/version.js.map +1 -0
- package/dist/worker.d.ts +17 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +53 -0
- package/dist/worker.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Research Tool Handler - Batch processing with dynamic token allocation
|
|
3
|
+
* Implements robust error handling that NEVER crashes
|
|
4
|
+
*/
|
|
5
|
+
import { ResearchClient } from '../clients/research.js';
|
|
6
|
+
import { FileAttachmentService } from '../services/file-attachment.js';
|
|
7
|
+
import { RESEARCH } from '../config/index.js';
|
|
8
|
+
import { classifyError } from '../utils/errors.js';
|
|
9
|
+
import { pMap } from '../utils/concurrency.js';
|
|
10
|
+
import { mcpLog, formatSuccess, formatError, formatBatchHeader, formatDuration, truncateText, TOKEN_BUDGETS, calculateTokenAllocation, } from './utils.js';
|
|
11
|
+
// Constants
|
|
12
|
+
const MIN_QUESTIONS = 1; // Allow single question for flexibility
|
|
13
|
+
const MAX_QUESTIONS = 10;
|
|
14
|
+
const SYSTEM_PROMPT = `You are an expert research consultant. Provide evidence-based, multi-perspective analysis.
|
|
15
|
+
|
|
16
|
+
METHODOLOGY:
|
|
17
|
+
- SOURCE DIVERSITY: Official docs, papers, blogs, case studies
|
|
18
|
+
- CURRENT + HISTORICAL: Latest developments AND context
|
|
19
|
+
- MULTIPLE PERSPECTIVES: Different approaches with pros/cons
|
|
20
|
+
- EVIDENCE-BASED: Claims backed by citations
|
|
21
|
+
|
|
22
|
+
FORMAT (high info density):
|
|
23
|
+
- CURRENT STATE: Status quo, what we know
|
|
24
|
+
- KEY INSIGHTS: Most important findings with evidence
|
|
25
|
+
- TRADE-OFFS: Competing priorities honestly analyzed
|
|
26
|
+
- PRACTICAL IMPLICATIONS: Real-world application
|
|
27
|
+
- WHAT'S CHANGING: Recent developments
|
|
28
|
+
|
|
29
|
+
Be dense with insights, light on filler. Use examples and citations.`;
|
|
30
|
+
/**
|
|
31
|
+
* Handle deep research request
|
|
32
|
+
* NEVER throws - always returns a valid response
|
|
33
|
+
*/
|
|
34
|
+
export async function handleDeepResearch(params) {
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
const questions = params.questions || [];
|
|
37
|
+
// Validation
|
|
38
|
+
if (questions.length < MIN_QUESTIONS) {
|
|
39
|
+
return {
|
|
40
|
+
content: formatError({
|
|
41
|
+
code: 'MIN_QUESTIONS',
|
|
42
|
+
message: `Minimum ${MIN_QUESTIONS} research question(s) required. Received: ${questions.length}`,
|
|
43
|
+
toolName: 'deep_research',
|
|
44
|
+
howToFix: ['Add at least one question with detailed context'],
|
|
45
|
+
}),
|
|
46
|
+
structuredContent: { error: true, message: `Minimum ${MIN_QUESTIONS} question(s) required` },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (questions.length > MAX_QUESTIONS) {
|
|
50
|
+
return {
|
|
51
|
+
content: formatError({
|
|
52
|
+
code: 'MAX_QUESTIONS',
|
|
53
|
+
message: `Maximum ${MAX_QUESTIONS} research questions allowed. Received: ${questions.length}`,
|
|
54
|
+
toolName: 'deep_research',
|
|
55
|
+
howToFix: [`Remove ${questions.length - MAX_QUESTIONS} question(s)`],
|
|
56
|
+
}),
|
|
57
|
+
structuredContent: { error: true, message: `Maximum ${MAX_QUESTIONS} questions allowed` },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const tokensPerQuestion = calculateTokenAllocation(questions.length, TOKEN_BUDGETS.RESEARCH);
|
|
61
|
+
mcpLog('info', `Starting batch research: ${questions.length} questions, ${tokensPerQuestion.toLocaleString()} tokens/question`, 'research');
|
|
62
|
+
// Initialize client safely
|
|
63
|
+
let client;
|
|
64
|
+
try {
|
|
65
|
+
client = new ResearchClient();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const err = classifyError(error);
|
|
69
|
+
return {
|
|
70
|
+
content: formatError({
|
|
71
|
+
code: 'CLIENT_INIT_FAILED',
|
|
72
|
+
message: `Failed to initialize research client: ${err.message}`,
|
|
73
|
+
toolName: 'deep_research',
|
|
74
|
+
howToFix: ['Check OPENROUTER_API_KEY is set'],
|
|
75
|
+
}),
|
|
76
|
+
structuredContent: { error: true, message: `Failed to initialize: ${err.message}` },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const fileService = new FileAttachmentService();
|
|
80
|
+
const results = [];
|
|
81
|
+
// Process questions with bounded concurrency (max 3 concurrent LLM calls)
|
|
82
|
+
const allResults = await pMap(questions, async (q, index) => {
|
|
83
|
+
try {
|
|
84
|
+
// Enhance question with file attachments if present
|
|
85
|
+
let enhancedQuestion = q.question;
|
|
86
|
+
if (q.file_attachments && q.file_attachments.length > 0) {
|
|
87
|
+
try {
|
|
88
|
+
const attachmentsMarkdown = await fileService.formatAttachments(q.file_attachments);
|
|
89
|
+
enhancedQuestion = q.question + attachmentsMarkdown;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// If attachment processing fails, continue with original question
|
|
93
|
+
mcpLog('warning', `Failed to process attachments for question ${index + 1}`, 'research');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ResearchClient.research() returns error in response instead of throwing
|
|
97
|
+
const response = await client.research({
|
|
98
|
+
question: enhancedQuestion,
|
|
99
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
100
|
+
reasoningEffort: RESEARCH.REASONING_EFFORT,
|
|
101
|
+
maxSearchResults: Math.min(RESEARCH.MAX_URLS, 20),
|
|
102
|
+
maxTokens: tokensPerQuestion,
|
|
103
|
+
});
|
|
104
|
+
// Check if response contains an error
|
|
105
|
+
if (response.error) {
|
|
106
|
+
return {
|
|
107
|
+
question: q.question,
|
|
108
|
+
content: response.content || '',
|
|
109
|
+
success: false,
|
|
110
|
+
error: response.error.message,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
question: q.question,
|
|
115
|
+
content: response.content || '',
|
|
116
|
+
success: !!response.content,
|
|
117
|
+
tokensUsed: response.usage?.totalTokens,
|
|
118
|
+
error: response.content ? undefined : 'Empty response received',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// Safety net - ResearchClient should not throw
|
|
123
|
+
const structuredError = classifyError(error);
|
|
124
|
+
return {
|
|
125
|
+
question: q.question,
|
|
126
|
+
content: '',
|
|
127
|
+
success: false,
|
|
128
|
+
error: structuredError.message,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}, 3); // Max 3 concurrent research calls
|
|
132
|
+
results.push(...allResults);
|
|
133
|
+
// Build markdown output
|
|
134
|
+
const successful = results.filter(r => r.success);
|
|
135
|
+
const failed = results.filter(r => !r.success);
|
|
136
|
+
const totalTokens = successful.reduce((sum, r) => sum + (r.tokensUsed || 0), 0);
|
|
137
|
+
const executionTime = Date.now() - startTime;
|
|
138
|
+
// Build 70/20/10 response
|
|
139
|
+
const batchHeader = formatBatchHeader({
|
|
140
|
+
title: `Deep Research Results`,
|
|
141
|
+
totalItems: questions.length,
|
|
142
|
+
successful: successful.length,
|
|
143
|
+
failed: failed.length,
|
|
144
|
+
tokensPerItem: tokensPerQuestion,
|
|
145
|
+
extras: {
|
|
146
|
+
'Total tokens used': totalTokens.toLocaleString(),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// Build questions data section
|
|
150
|
+
const questionsData = [];
|
|
151
|
+
for (let i = 0; i < results.length; i++) {
|
|
152
|
+
const r = results[i];
|
|
153
|
+
const preview = truncateText(r.question, 100);
|
|
154
|
+
questionsData.push(`## Question ${i + 1}: ${preview}\n`);
|
|
155
|
+
if (r.success) {
|
|
156
|
+
questionsData.push(r.content);
|
|
157
|
+
if (r.tokensUsed) {
|
|
158
|
+
questionsData.push(`\n*Tokens used: ${r.tokensUsed.toLocaleString()}*`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
questionsData.push(`**❌ Error:** ${r.error}`);
|
|
163
|
+
}
|
|
164
|
+
questionsData.push('\n---\n');
|
|
165
|
+
}
|
|
166
|
+
const nextSteps = [
|
|
167
|
+
successful.length > 0 ? 'Scrape mentioned sources: scrape_links(urls=[...extracted URLs...], use_llm=true)' : null,
|
|
168
|
+
failed.length > 0 ? 'Retry failed questions with more specific context' : null,
|
|
169
|
+
'Search Reddit for community perspective: search_reddit(queries=[...related topics...])',
|
|
170
|
+
].filter(Boolean);
|
|
171
|
+
const formattedContent = formatSuccess({
|
|
172
|
+
title: `Research Complete (${successful.length}/${questions.length})`,
|
|
173
|
+
summary: batchHeader,
|
|
174
|
+
data: questionsData.join('\n'),
|
|
175
|
+
nextSteps,
|
|
176
|
+
metadata: {
|
|
177
|
+
'Execution time': formatDuration(executionTime),
|
|
178
|
+
'Token budget': TOKEN_BUDGETS.RESEARCH.toLocaleString(),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
mcpLog('info', `Research completed: ${successful.length}/${questions.length} successful, ${totalTokens.toLocaleString()} tokens`, 'research');
|
|
182
|
+
return {
|
|
183
|
+
content: formattedContent,
|
|
184
|
+
structuredContent: {
|
|
185
|
+
totalQuestions: questions.length,
|
|
186
|
+
successful: successful.length,
|
|
187
|
+
failed: failed.length,
|
|
188
|
+
tokensPerQuestion,
|
|
189
|
+
totalTokensUsed: totalTokens,
|
|
190
|
+
results,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=research.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research.js","sourceRoot":"","sources":["../../src/tools/research.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrape Links Tool Handler
|
|
3
|
+
* Implements robust error handling that NEVER crashes the MCP server
|
|
4
|
+
*/
|
|
5
|
+
import type { ScrapeLinksParams, ScrapeLinksOutput } from '../schemas/scrape-links.js';
|
|
6
|
+
/**
|
|
7
|
+
* Handle scrape links request
|
|
8
|
+
* NEVER throws - always returns a valid response with content and metadata
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleScrapeLinks(params: ScrapeLinksParams): Promise<{
|
|
11
|
+
content: string;
|
|
12
|
+
structuredContent: ScrapeLinksOutput;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=scrape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrape.d.ts","sourceRoot":"","sources":["../../src/tools/scrape.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAiCvF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAoNpE"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrape Links Tool Handler
|
|
3
|
+
* Implements robust error handling that NEVER crashes the MCP server
|
|
4
|
+
*/
|
|
5
|
+
import { ScraperClient } from '../clients/scraper.js';
|
|
6
|
+
import { MarkdownCleaner } from '../services/markdown-cleaner.js';
|
|
7
|
+
import { createLLMProcessor, processContentWithLLM } from '../services/llm-processor.js';
|
|
8
|
+
import { removeMetaTags } from '../utils/markdown-formatter.js';
|
|
9
|
+
import { SCRAPER } from '../config/index.js';
|
|
10
|
+
import { getToolConfig } from '../config/loader.js';
|
|
11
|
+
import { classifyError } from '../utils/errors.js';
|
|
12
|
+
import { pMap } from '../utils/concurrency.js';
|
|
13
|
+
import { mcpLog, formatSuccess, formatError, formatBatchHeader, formatDuration, TOKEN_BUDGETS, calculateTokenAllocation, } from './utils.js';
|
|
14
|
+
// Module-level singleton - MarkdownCleaner is stateless
|
|
15
|
+
const markdownCleaner = new MarkdownCleaner();
|
|
16
|
+
// Get extraction suffix from YAML config (fallback to hardcoded if not found)
|
|
17
|
+
function getExtractionSuffix() {
|
|
18
|
+
const config = getToolConfig('scrape_links');
|
|
19
|
+
return config?.limits?.extraction_suffix || SCRAPER.EXTRACTION_SUFFIX;
|
|
20
|
+
}
|
|
21
|
+
function enhanceExtractionInstruction(instruction) {
|
|
22
|
+
const base = instruction || 'Extract the main content and key information from this page.';
|
|
23
|
+
return `${base}\n\n${getExtractionSuffix()}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Handle scrape links request
|
|
27
|
+
* NEVER throws - always returns a valid response with content and metadata
|
|
28
|
+
*/
|
|
29
|
+
export async function handleScrapeLinks(params) {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
// Helper to create error response
|
|
32
|
+
const createErrorResponse = (code, message, retryable = false) => ({
|
|
33
|
+
content: formatError({
|
|
34
|
+
code,
|
|
35
|
+
message,
|
|
36
|
+
retryable,
|
|
37
|
+
toolName: 'scrape_links',
|
|
38
|
+
howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,
|
|
39
|
+
}),
|
|
40
|
+
structuredContent: {
|
|
41
|
+
content: message,
|
|
42
|
+
metadata: {
|
|
43
|
+
total_urls: params.urls?.length || 0,
|
|
44
|
+
successful: 0,
|
|
45
|
+
failed: params.urls?.length || 0,
|
|
46
|
+
total_credits: 0,
|
|
47
|
+
execution_time_ms: Date.now() - startTime,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
// Validate params
|
|
52
|
+
if (!params.urls || params.urls.length === 0) {
|
|
53
|
+
return createErrorResponse('NO_URLS', 'No URLs provided');
|
|
54
|
+
}
|
|
55
|
+
// Filter out invalid URLs early
|
|
56
|
+
const validUrls = [];
|
|
57
|
+
const invalidUrls = [];
|
|
58
|
+
for (const url of params.urls) {
|
|
59
|
+
try {
|
|
60
|
+
new URL(url);
|
|
61
|
+
validUrls.push(url);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
invalidUrls.push(url);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (validUrls.length === 0) {
|
|
68
|
+
return createErrorResponse('INVALID_URLS', `All ${params.urls.length} URLs are invalid`);
|
|
69
|
+
}
|
|
70
|
+
const tokensPerUrl = calculateTokenAllocation(validUrls.length, TOKEN_BUDGETS.SCRAPER);
|
|
71
|
+
const totalBatches = Math.ceil(validUrls.length / SCRAPER.BATCH_SIZE);
|
|
72
|
+
mcpLog('info', `Starting scrape: ${validUrls.length} URL(s), ${tokensPerUrl} tokens/URL, ${totalBatches} batch(es)`, 'scrape');
|
|
73
|
+
// Initialize clients safely
|
|
74
|
+
let client;
|
|
75
|
+
try {
|
|
76
|
+
client = new ScraperClient();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const err = classifyError(error);
|
|
80
|
+
return createErrorResponse('CLIENT_INIT_FAILED', `Failed to initialize scraper: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
const llmProcessor = createLLMProcessor(); // Returns null if not configured
|
|
83
|
+
const enhancedInstruction = params.use_llm
|
|
84
|
+
? enhanceExtractionInstruction(params.what_to_extract)
|
|
85
|
+
: undefined;
|
|
86
|
+
// Scrape URLs - scrapeMultiple NEVER throws
|
|
87
|
+
const results = await client.scrapeMultiple(validUrls, { timeout: params.timeout });
|
|
88
|
+
mcpLog('info', `Scraping complete. Processing ${results.length} results...`, 'scrape');
|
|
89
|
+
let successful = 0;
|
|
90
|
+
let failed = 0;
|
|
91
|
+
let totalCredits = 0;
|
|
92
|
+
let llmErrors = 0;
|
|
93
|
+
const contents = [];
|
|
94
|
+
// Add invalid URLs to failed count
|
|
95
|
+
for (const invalidUrl of invalidUrls) {
|
|
96
|
+
failed++;
|
|
97
|
+
contents.push(`## ${invalidUrl}\n\n❌ Invalid URL format`);
|
|
98
|
+
}
|
|
99
|
+
const successItems = [];
|
|
100
|
+
for (let i = 0; i < results.length; i++) {
|
|
101
|
+
const result = results[i];
|
|
102
|
+
if (!result) {
|
|
103
|
+
failed++;
|
|
104
|
+
contents.push(`## Unknown URL\n\n❌ No result returned`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
mcpLog('debug', `[${i + 1}/${results.length}] Processing ${result.url}`, 'scrape');
|
|
108
|
+
// Check for errors in result
|
|
109
|
+
if (result.error || result.statusCode < 200 || result.statusCode >= 300) {
|
|
110
|
+
failed++;
|
|
111
|
+
const errorMsg = result.error?.message || result.content || `HTTP ${result.statusCode}`;
|
|
112
|
+
contents.push(`## ${result.url}\n\n❌ Failed to scrape: ${errorMsg}`);
|
|
113
|
+
mcpLog('warning', `[${i + 1}/${results.length}] Failed: ${errorMsg}`, 'scrape');
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Success case
|
|
117
|
+
successful++;
|
|
118
|
+
totalCredits += result.credits;
|
|
119
|
+
// Process content safely (CPU-bound, fast)
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = markdownCleaner.processContent(result.content);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
content = result.content;
|
|
126
|
+
}
|
|
127
|
+
successItems.push({ url: result.url, content, index: i });
|
|
128
|
+
}
|
|
129
|
+
// Pass 2: Parallel LLM extraction for successful results (I/O-bound)
|
|
130
|
+
if (params.use_llm && llmProcessor && successItems.length > 0) {
|
|
131
|
+
mcpLog('info', `Starting parallel LLM extraction for ${successItems.length} pages (concurrency: 3)`, 'scrape');
|
|
132
|
+
const llmResults = await pMap(successItems, async (item) => {
|
|
133
|
+
mcpLog('debug', `LLM extracting ${item.url} (${tokensPerUrl} tokens)...`, 'scrape');
|
|
134
|
+
const llmResult = await processContentWithLLM(item.content, { use_llm: params.use_llm, what_to_extract: enhancedInstruction, max_tokens: tokensPerUrl }, llmProcessor);
|
|
135
|
+
if (llmResult.processed) {
|
|
136
|
+
mcpLog('debug', `LLM extraction complete for ${item.url}`, 'scrape');
|
|
137
|
+
return { ...item, content: llmResult.content };
|
|
138
|
+
}
|
|
139
|
+
llmErrors++;
|
|
140
|
+
mcpLog('warning', `LLM extraction skipped for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');
|
|
141
|
+
return item; // Graceful degradation — use original cleaned content
|
|
142
|
+
}, 3);
|
|
143
|
+
// Update successItems with LLM-processed content
|
|
144
|
+
for (let i = 0; i < llmResults.length; i++) {
|
|
145
|
+
successItems[i] = llmResults[i];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Pass 3: Final assembly — remove meta tags and build content entries
|
|
149
|
+
for (const item of successItems) {
|
|
150
|
+
let content = item.content;
|
|
151
|
+
try {
|
|
152
|
+
content = removeMetaTags(content);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// If this fails, just use the content as-is
|
|
156
|
+
}
|
|
157
|
+
contents.push(`## ${item.url}\n\n${content}`);
|
|
158
|
+
}
|
|
159
|
+
const executionTime = Date.now() - startTime;
|
|
160
|
+
mcpLog('info', `Completed: ${successful} successful, ${failed} failed, ${totalCredits} credits used`, 'scrape');
|
|
161
|
+
// Build 70/20/10 response
|
|
162
|
+
const batchHeader = formatBatchHeader({
|
|
163
|
+
title: `Scraped Content (${params.urls.length} URLs)`,
|
|
164
|
+
totalItems: params.urls.length,
|
|
165
|
+
successful,
|
|
166
|
+
failed,
|
|
167
|
+
tokensPerItem: tokensPerUrl,
|
|
168
|
+
batches: totalBatches,
|
|
169
|
+
extras: {
|
|
170
|
+
'Credits used': totalCredits,
|
|
171
|
+
...(llmErrors > 0 ? { 'LLM extraction failures': llmErrors } : {}),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const nextSteps = [
|
|
175
|
+
successful > 0 ? `Extract specific data: scrape_links(urls=[...], use_llm=true, what_to_extract="Extract pricing | features | testimonials")` : null,
|
|
176
|
+
failed > 0 ? `Retry failed URLs with longer timeout: scrape_links(urls=[...], timeout=60)` : null,
|
|
177
|
+
'Research further: deep_research(questions=[{question: "Based on scraped content..."}])',
|
|
178
|
+
].filter(Boolean);
|
|
179
|
+
const formattedContent = formatSuccess({
|
|
180
|
+
title: 'Scraping Complete',
|
|
181
|
+
summary: batchHeader,
|
|
182
|
+
data: contents.join('\n\n---\n\n'),
|
|
183
|
+
nextSteps,
|
|
184
|
+
metadata: {
|
|
185
|
+
'Execution time': formatDuration(executionTime),
|
|
186
|
+
'Token budget': TOKEN_BUDGETS.SCRAPER.toLocaleString(),
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
const metadata = {
|
|
190
|
+
total_urls: params.urls.length,
|
|
191
|
+
successful,
|
|
192
|
+
failed,
|
|
193
|
+
total_credits: totalCredits,
|
|
194
|
+
execution_time_ms: executionTime,
|
|
195
|
+
tokens_per_url: tokensPerUrl,
|
|
196
|
+
total_token_budget: TOKEN_BUDGETS.SCRAPER,
|
|
197
|
+
batches_processed: totalBatches,
|
|
198
|
+
};
|
|
199
|
+
return { content: formattedContent, structuredContent: { content: formattedContent, metadata } };
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=scrape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrape.js","sourceRoot":"","sources":["../../src/tools/scrape.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAEpB,wDAAwD;AACxD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;AAE9C,8EAA8E;AAC9E,SAAS,mBAAmB;IAC1B,MAAM,MAAM,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IAC7C,OAAO,MAAM,EAAE,MAAM,EAAE,iBAA2B,IAAI,OAAO,CAAC,iBAAiB,CAAC;AAClF,CAAC;AAED,SAAS,4BAA4B,CAAC,WAA+B;IACnE,MAAM,IAAI,GAAG,WAAW,IAAI,8DAA8D,CAAC;IAC3F,OAAO,GAAG,IAAI,OAAO,mBAAmB,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAyB;IAEzB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,kCAAkC;IAClC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,SAAS,GAAG,KAAK,EAA6D,EAAE,CAAC,CAAC;QAC5I,OAAO,EAAE,WAAW,CAAC;YACnB,IAAI;YACJ,OAAO;YACP,SAAS;YACT,QAAQ,EAAE,cAAc;YACxB,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9E,CAAC;QACF,iBAAiB,EAAE;YACjB,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE;gBACR,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;gBACpC,UAAU,EAAE,CAAC;gBACb,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;gBAChC,aAAa,EAAE,CAAC;gBAChB,iBAAiB,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;aAC1C;SACF;KACF,CAAC,CAAC;IAEH,kBAAkB;IAClB,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,mBAAmB,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAC5D,CAAC;IAED,gCAAgC;IAChC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YACb,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,mBAAmB,CAAC,cAAc,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,YAAY,GAAG,wBAAwB,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACvF,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEtE,MAAM,CAAC,MAAM,EAAE,oBAAoB,SAAS,CAAC,MAAM,YAAY,YAAY,gBAAgB,YAAY,YAAY,EAAE,QAAQ,CAAC,CAAC;IAE/H,4BAA4B;IAC5B,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,mBAAmB,CAAC,oBAAoB,EAAE,iCAAiC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAC,CAAC,iCAAiC;IAE5E,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO;QACxC,CAAC,CAAC,4BAA4B,CAAC,MAAM,CAAC,eAAe,CAAC;QACtD,CAAC,CAAC,SAAS,CAAC;IAEd,4CAA4C;IAC5C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpF,MAAM,CAAC,MAAM,EAAE,iCAAiC,OAAO,CAAC,MAAM,aAAa,EAAE,QAAQ,CAAC,CAAC;IAEvF,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,mCAAmC;IACnC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,EAAE,CAAC;QACT,QAAQ,CAAC,IAAI,CAAC,MAAM,UAAU,0BAA0B,CAAC,CAAC;IAC5D,CAAC;IAQD,MAAM,YAAY,GAAsB,EAAE,CAAC;IAE3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;YACT,QAAQ,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;YACxD,SAAS;QACX,CAAC;QAED,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,gBAAgB,MAAM,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;QAEnF,6BAA6B;QAC7B,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,UAAU,GAAG,GAAG,IAAI,MAAM,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;YACxE,MAAM,EAAE,CAAC;YACT,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,QAAQ,MAAM,CAAC,UAAU,EAAE,CAAC;YACxF,QAAQ,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,GAAG,2BAA2B,QAAQ,EAAE,CAAC,CAAC;YACrE,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,aAAa,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;YAChF,SAAS;QACX,CAAC;QAED,eAAe;QACf,UAAU,EAAE,CAAC;QACb,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;QAE/B,2CAA2C;QAC3C,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC3B,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,qEAAqE;IACrE,IAAI,MAAM,CAAC,OAAO,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9D,MAAM,CAAC,MAAM,EAAE,wCAAwC,YAAY,CAAC,MAAM,yBAAyB,EAAE,QAAQ,CAAC,CAAC;QAE/G,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACzD,MAAM,CAAC,OAAO,EAAE,kBAAkB,IAAI,CAAC,GAAG,KAAK,YAAY,aAAa,EAAE,QAAQ,CAAC,CAAC;YAEpF,MAAM,SAAS,GAAG,MAAM,qBAAqB,CAC3C,IAAI,CAAC,OAAO,EACZ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,YAAY,EAAE,EAC3F,YAAY,CACb,CAAC;YAEF,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;gBACxB,MAAM,CAAC,OAAO,EAAE,+BAA+B,IAAI,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;gBACrE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC;YACjD,CAAC;YAED,SAAS,EAAE,CAAC;YACZ,MAAM,CAAC,SAAS,EAAE,8BAA8B,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,KAAK,IAAI,gBAAgB,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC9G,OAAO,IAAI,CAAC,CAAC,sDAAsD;QACrE,CAAC,EAAE,CAAC,CAAC,CAAC;QAEN,iDAAiD;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,YAAY,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,OAAO,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAE7C,MAAM,CAAC,MAAM,EAAE,cAAc,UAAU,gBAAgB,MAAM,YAAY,YAAY,eAAe,EAAE,QAAQ,CAAC,CAAC;IAEhH,0BAA0B;IAC1B,MAAM,WAAW,GAAG,iBAAiB,CAAC;QACpC,KAAK,EAAE,oBAAoB,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ;QACrD,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;QAC9B,UAAU;QACV,MAAM;QACN,aAAa,EAAE,YAAY;QAC3B,OAAO,EAAE,YAAY;QACrB,MAAM,EAAE;YACN,cAAc,EAAE,YAAY;YAC5B,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnE;KACF,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG;QAChB,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,4HAA4H,CAAC,CAAC,CAAC,IAAI;QACpJ,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,6EAA6E,CAAC,CAAC,CAAC,IAAI;QACjG,wFAAwF;KACzF,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IAE9B,MAAM,gBAAgB,GAAG,aAAa,CAAC;QACrC,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,WAAW;QACpB,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC;QAClC,SAAS;QACT,QAAQ,EAAE;YACR,gBAAgB,EAAE,cAAc,CAAC,aAAa,CAAC;YAC/C,cAAc,EAAE,aAAa,CAAC,OAAO,CAAC,cAAc,EAAE;SACvD;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG;QACf,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;QAC9B,UAAU;QACV,MAAM;QACN,aAAa,EAAE,YAAY;QAC3B,iBAAiB,EAAE,aAAa;QAChC,cAAc,EAAE,YAAY;QAC5B,kBAAkB,EAAE,aAAa,CAAC,OAAO;QACzC,iBAAiB,EAAE,YAAY;KAChC,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,CAAC;AACnG,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search Tool Handler
|
|
3
|
+
* NEVER throws - always returns structured response for graceful degradation
|
|
4
|
+
*/
|
|
5
|
+
import type { WebSearchParams, WebSearchOutput } from '../schemas/web-search.js';
|
|
6
|
+
export declare function handleWebSearch(params: WebSearchParams): Promise<{
|
|
7
|
+
content: string;
|
|
8
|
+
structuredContent: WebSearchOutput;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAyBjF,wBAAsB,eAAe,CACnC,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,eAAe,CAAA;CAAE,CAAC,CA2JlE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search Tool Handler
|
|
3
|
+
* NEVER throws - always returns structured response for graceful degradation
|
|
4
|
+
*/
|
|
5
|
+
import { SearchClient } from '../clients/search.js';
|
|
6
|
+
import { aggregateAndRank, buildUrlLookup, lookupUrl, generateEnhancedOutput, markConsensus, } from '../utils/url-aggregator.js';
|
|
7
|
+
import { CTR_WEIGHTS } from '../config/index.js';
|
|
8
|
+
import { classifyError } from '../utils/errors.js';
|
|
9
|
+
import { mcpLog, formatError, formatDuration, } from './utils.js';
|
|
10
|
+
function getPositionScore(position) {
|
|
11
|
+
if (position >= 1 && position <= 10) {
|
|
12
|
+
return CTR_WEIGHTS[position] ?? 0;
|
|
13
|
+
}
|
|
14
|
+
return Math.max(0, 10 - (position - 10) * 0.5);
|
|
15
|
+
}
|
|
16
|
+
export async function handleWebSearch(params) {
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
mcpLog('info', `Searching for ${params.keywords.length} keyword(s)`, 'search');
|
|
20
|
+
const client = new SearchClient();
|
|
21
|
+
const response = await client.searchMultiple(params.keywords);
|
|
22
|
+
const aggregation = aggregateAndRank(response.searches, 5);
|
|
23
|
+
const urlLookup = buildUrlLookup(aggregation.rankedUrls);
|
|
24
|
+
const consensusUrls = aggregation.rankedUrls.filter(url => url.frequency >= aggregation.frequencyThreshold);
|
|
25
|
+
let markdown = '';
|
|
26
|
+
if (consensusUrls.length > 0) {
|
|
27
|
+
markdown += generateEnhancedOutput(consensusUrls, params.keywords, aggregation.totalUniqueUrls, aggregation.frequencyThreshold, aggregation.thresholdNote);
|
|
28
|
+
markdown += '\n---\n\n';
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
markdown += `## The Perfect Search Results (Aggregated from ${response.totalKeywords} Queries)\n\n`;
|
|
32
|
+
markdown += `> *No high-consensus URLs found across searches. Results may be highly diverse.*\n\n`;
|
|
33
|
+
markdown += `---\n\n`;
|
|
34
|
+
}
|
|
35
|
+
// Limit output based on number of queries to keep under ~20k tokens
|
|
36
|
+
const MAX_QUERIES_SHOWN = 15;
|
|
37
|
+
const MAX_RESULTS_PER_QUERY = response.totalKeywords > 10 ? 5 : 10;
|
|
38
|
+
const queriesToShow = response.searches.slice(0, MAX_QUERIES_SHOWN);
|
|
39
|
+
const queriesOmitted = response.searches.length - queriesToShow.length;
|
|
40
|
+
markdown += `## 📊 Full Search Results by Query`;
|
|
41
|
+
if (queriesOmitted > 0) {
|
|
42
|
+
markdown += ` (showing ${queriesToShow.length} of ${response.searches.length})`;
|
|
43
|
+
}
|
|
44
|
+
markdown += `\n\n`;
|
|
45
|
+
let totalResults = 0;
|
|
46
|
+
queriesToShow.forEach((search, index) => {
|
|
47
|
+
markdown += `### Query ${index + 1}: "${search.keyword}"\n\n`;
|
|
48
|
+
search.results.slice(0, MAX_RESULTS_PER_QUERY).forEach((result, resultIndex) => {
|
|
49
|
+
const position = resultIndex + 1;
|
|
50
|
+
const positionScore = getPositionScore(position);
|
|
51
|
+
const rankedUrl = lookupUrl(result.link, urlLookup);
|
|
52
|
+
const frequency = rankedUrl?.frequency ?? 1;
|
|
53
|
+
const consensusMark = markConsensus(frequency);
|
|
54
|
+
const consensusInfo = rankedUrl
|
|
55
|
+
? `${consensusMark} (${frequency} searches)`
|
|
56
|
+
: `${consensusMark} (1 search)`;
|
|
57
|
+
markdown += `${position}. **[${result.title}](${result.link})** — Position ${position} | Score: ${positionScore.toFixed(1)} | Consensus: ${consensusInfo}\n`;
|
|
58
|
+
if (result.snippet) {
|
|
59
|
+
let snippet = result.snippet;
|
|
60
|
+
if (snippet.length > 150) {
|
|
61
|
+
snippet = snippet.substring(0, 147) + '...';
|
|
62
|
+
}
|
|
63
|
+
if (result.date) {
|
|
64
|
+
markdown += ` - *${result.date}* — ${snippet}\n`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
markdown += ` - ${snippet}\n`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
markdown += '\n';
|
|
71
|
+
totalResults++;
|
|
72
|
+
});
|
|
73
|
+
if (search.related && search.related.length > 0) {
|
|
74
|
+
const relatedSuggestions = search.related
|
|
75
|
+
.slice(0, 5)
|
|
76
|
+
.map((r) => `\`${r}\``)
|
|
77
|
+
.join(', ');
|
|
78
|
+
markdown += `*Related:* ${relatedSuggestions}\n\n`;
|
|
79
|
+
}
|
|
80
|
+
if (index < queriesToShow.length - 1) {
|
|
81
|
+
markdown += `---\n\n`;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (queriesOmitted > 0) {
|
|
85
|
+
markdown += `\n---\n\n> *${queriesOmitted} additional queries not shown. Consensus URLs above include all ${response.searches.length} queries.*\n`;
|
|
86
|
+
}
|
|
87
|
+
const executionTime = Date.now() - startTime;
|
|
88
|
+
mcpLog('info', `Search completed: ${totalResults} results, ${aggregation.totalUniqueUrls} unique URLs, ${consensusUrls.length} consensus`, 'search');
|
|
89
|
+
// Add Next Steps section
|
|
90
|
+
const nextSteps = [
|
|
91
|
+
consensusUrls.length > 0 ? `Scrape top consensus URLs: scrape_links(urls=[${consensusUrls.slice(0, 3).map(u => `"${u.url}"`).join(', ')}], use_llm=true)` : null,
|
|
92
|
+
'Get Reddit perspective: search_reddit(queries=[...related terms...])',
|
|
93
|
+
'Deep research: deep_research(questions=[{question: "Based on search results..."}])',
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
markdown += '\n\n---\n\n**Next Steps:**\n';
|
|
96
|
+
nextSteps.forEach(step => { markdown += `→ ${step}\n`; });
|
|
97
|
+
markdown += `\n---\n*${formatDuration(executionTime)} | ${aggregation.totalUniqueUrls} unique URLs | ${consensusUrls.length} consensus*`;
|
|
98
|
+
const metadata = {
|
|
99
|
+
total_keywords: response.totalKeywords,
|
|
100
|
+
total_results: totalResults,
|
|
101
|
+
execution_time_ms: executionTime,
|
|
102
|
+
total_unique_urls: aggregation.totalUniqueUrls,
|
|
103
|
+
consensus_url_count: consensusUrls.length,
|
|
104
|
+
frequency_threshold: aggregation.frequencyThreshold,
|
|
105
|
+
};
|
|
106
|
+
return { content: markdown, structuredContent: { content: markdown, metadata } };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const structuredError = classifyError(error);
|
|
110
|
+
const executionTime = Date.now() - startTime;
|
|
111
|
+
mcpLog('error', `web_search: ${structuredError.message}`, 'search');
|
|
112
|
+
const errorContent = formatError({
|
|
113
|
+
code: structuredError.code,
|
|
114
|
+
message: structuredError.message,
|
|
115
|
+
retryable: structuredError.retryable,
|
|
116
|
+
toolName: 'web_search',
|
|
117
|
+
howToFix: ['Verify SERPER_API_KEY is set correctly'],
|
|
118
|
+
alternatives: [
|
|
119
|
+
'search_reddit(queries=[...]) for Reddit-specific results',
|
|
120
|
+
'deep_research(questions=[...]) for AI-synthesized research',
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
content: errorContent,
|
|
125
|
+
structuredContent: {
|
|
126
|
+
content: errorContent,
|
|
127
|
+
metadata: {
|
|
128
|
+
total_keywords: params.keywords.length,
|
|
129
|
+
total_results: 0,
|
|
130
|
+
execution_time_ms: executionTime,
|
|
131
|
+
errorCode: structuredError.code,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"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"}
|