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.
Files changed (119) hide show
  1. package/README.md +635 -0
  2. package/dist/clients/reddit.d.ts +74 -0
  3. package/dist/clients/reddit.d.ts.map +1 -0
  4. package/dist/clients/reddit.js +305 -0
  5. package/dist/clients/reddit.js.map +1 -0
  6. package/dist/clients/research.d.ts +67 -0
  7. package/dist/clients/research.d.ts.map +1 -0
  8. package/dist/clients/research.js +252 -0
  9. package/dist/clients/research.js.map +1 -0
  10. package/dist/clients/scraper.d.ts +71 -0
  11. package/dist/clients/scraper.d.ts.map +1 -0
  12. package/dist/clients/scraper.js +321 -0
  13. package/dist/clients/scraper.js.map +1 -0
  14. package/dist/clients/search.d.ts +62 -0
  15. package/dist/clients/search.d.ts.map +1 -0
  16. package/dist/clients/search.js +219 -0
  17. package/dist/clients/search.js.map +1 -0
  18. package/dist/config/index.d.ts +62 -0
  19. package/dist/config/index.d.ts.map +1 -0
  20. package/dist/config/index.js +142 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/config/loader.d.ts +40 -0
  23. package/dist/config/loader.d.ts.map +1 -0
  24. package/dist/config/loader.js +305 -0
  25. package/dist/config/loader.js.map +1 -0
  26. package/dist/config/types.d.ts +81 -0
  27. package/dist/config/types.d.ts.map +1 -0
  28. package/dist/config/types.js +6 -0
  29. package/dist/config/types.js.map +1 -0
  30. package/dist/config/yaml/tools.yaml +130 -0
  31. package/dist/index.d.ts +7 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +271 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/schemas/deep-research.d.ts +64 -0
  36. package/dist/schemas/deep-research.d.ts.map +1 -0
  37. package/dist/schemas/deep-research.js +224 -0
  38. package/dist/schemas/deep-research.js.map +1 -0
  39. package/dist/schemas/scrape-links.d.ts +32 -0
  40. package/dist/schemas/scrape-links.d.ts.map +1 -0
  41. package/dist/schemas/scrape-links.js +34 -0
  42. package/dist/schemas/scrape-links.js.map +1 -0
  43. package/dist/schemas/web-search.d.ts +22 -0
  44. package/dist/schemas/web-search.d.ts.map +1 -0
  45. package/dist/schemas/web-search.js +21 -0
  46. package/dist/schemas/web-search.js.map +1 -0
  47. package/dist/services/file-attachment.d.ts +30 -0
  48. package/dist/services/file-attachment.d.ts.map +1 -0
  49. package/dist/services/file-attachment.js +199 -0
  50. package/dist/services/file-attachment.js.map +1 -0
  51. package/dist/services/llm-processor.d.ts +27 -0
  52. package/dist/services/llm-processor.d.ts.map +1 -0
  53. package/dist/services/llm-processor.js +179 -0
  54. package/dist/services/llm-processor.js.map +1 -0
  55. package/dist/services/markdown-cleaner.d.ts +8 -0
  56. package/dist/services/markdown-cleaner.d.ts.map +1 -0
  57. package/dist/services/markdown-cleaner.js +44 -0
  58. package/dist/services/markdown-cleaner.js.map +1 -0
  59. package/dist/tools/definitions.d.ts +16 -0
  60. package/dist/tools/definitions.d.ts.map +1 -0
  61. package/dist/tools/definitions.js +17 -0
  62. package/dist/tools/definitions.js.map +1 -0
  63. package/dist/tools/reddit.d.ts +14 -0
  64. package/dist/tools/reddit.d.ts.map +1 -0
  65. package/dist/tools/reddit.js +213 -0
  66. package/dist/tools/reddit.js.map +1 -0
  67. package/dist/tools/registry.d.ts +71 -0
  68. package/dist/tools/registry.d.ts.map +1 -0
  69. package/dist/tools/registry.js +242 -0
  70. package/dist/tools/registry.js.map +1 -0
  71. package/dist/tools/research.d.ts +14 -0
  72. package/dist/tools/research.d.ts.map +1 -0
  73. package/dist/tools/research.js +194 -0
  74. package/dist/tools/research.js.map +1 -0
  75. package/dist/tools/scrape.d.ts +14 -0
  76. package/dist/tools/scrape.d.ts.map +1 -0
  77. package/dist/tools/scrape.js +201 -0
  78. package/dist/tools/scrape.js.map +1 -0
  79. package/dist/tools/search.d.ts +10 -0
  80. package/dist/tools/search.d.ts.map +1 -0
  81. package/dist/tools/search.js +137 -0
  82. package/dist/tools/search.js.map +1 -0
  83. package/dist/tools/utils.d.ts +105 -0
  84. package/dist/tools/utils.d.ts.map +1 -0
  85. package/dist/tools/utils.js +159 -0
  86. package/dist/tools/utils.js.map +1 -0
  87. package/dist/utils/concurrency.d.ts +29 -0
  88. package/dist/utils/concurrency.d.ts.map +1 -0
  89. package/dist/utils/concurrency.js +73 -0
  90. package/dist/utils/concurrency.js.map +1 -0
  91. package/dist/utils/errors.d.ts +77 -0
  92. package/dist/utils/errors.d.ts.map +1 -0
  93. package/dist/utils/errors.js +335 -0
  94. package/dist/utils/errors.js.map +1 -0
  95. package/dist/utils/logger.d.ts +39 -0
  96. package/dist/utils/logger.d.ts.map +1 -0
  97. package/dist/utils/logger.js +57 -0
  98. package/dist/utils/logger.js.map +1 -0
  99. package/dist/utils/markdown-formatter.d.ts +5 -0
  100. package/dist/utils/markdown-formatter.d.ts.map +1 -0
  101. package/dist/utils/markdown-formatter.js +15 -0
  102. package/dist/utils/markdown-formatter.js.map +1 -0
  103. package/dist/utils/response.d.ts +88 -0
  104. package/dist/utils/response.d.ts.map +1 -0
  105. package/dist/utils/response.js +151 -0
  106. package/dist/utils/response.js.map +1 -0
  107. package/dist/utils/url-aggregator.d.ts +90 -0
  108. package/dist/utils/url-aggregator.d.ts.map +1 -0
  109. package/dist/utils/url-aggregator.js +502 -0
  110. package/dist/utils/url-aggregator.js.map +1 -0
  111. package/dist/version.d.ts +30 -0
  112. package/dist/version.d.ts.map +1 -0
  113. package/dist/version.js +60 -0
  114. package/dist/version.js.map +1 -0
  115. package/dist/worker.d.ts +17 -0
  116. package/dist/worker.d.ts.map +1 -0
  117. package/dist/worker.js +53 -0
  118. package/dist/worker.js.map +1 -0
  119. 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":"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"}
@@ -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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"}