research-powerpack-mcp 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Handler Registry - Central tool registration and execution
3
+ * Eliminates repetitive if/else routing with declarative registration
4
+ */
5
+ import { z, ZodError } from 'zod';
6
+ import { McpError, ErrorCode as McpErrorCode } from '@modelcontextprotocol/sdk/types.js';
7
+ import { parseEnv, getCapabilities, getMissingEnvMessage } from '../config/index.js';
8
+ import { classifyError, createToolErrorFromStructured } from '../utils/errors.js';
9
+ // Import schemas
10
+ import { deepResearchParamsSchema } from '../schemas/deep-research.js';
11
+ import { scrapeLinksParamsSchema } from '../schemas/scrape-links.js';
12
+ import { webSearchParamsSchema } from '../schemas/web-search.js';
13
+ // Import handlers
14
+ import { handleSearchReddit, handleGetRedditPosts } from './reddit.js';
15
+ import { handleDeepResearch } from './research.js';
16
+ import { handleScrapeLinks } from './scrape.js';
17
+ import { handleWebSearch } from './search.js';
18
+ // ============================================================================
19
+ // Schemas for Simple Tools (inline definitions)
20
+ // ============================================================================
21
+ const searchRedditParamsSchema = z.object({
22
+ queries: z.array(z.string()).min(10).max(50),
23
+ date_after: z.string().optional(),
24
+ });
25
+ const getRedditPostParamsSchema = z.object({
26
+ urls: z.array(z.string()).min(2).max(50),
27
+ fetch_comments: z.boolean().default(true),
28
+ max_comments: z.number().default(100),
29
+ });
30
+ // ============================================================================
31
+ // Handler Wrappers
32
+ // ============================================================================
33
+ const env = parseEnv();
34
+ /**
35
+ * Wrapper for search_reddit handler
36
+ */
37
+ async function searchRedditHandler(params) {
38
+ const p = params;
39
+ return handleSearchReddit(p.queries, env.SEARCH_API_KEY, p.date_after);
40
+ }
41
+ /**
42
+ * Wrapper for get_reddit_post handler
43
+ */
44
+ async function getRedditPostHandler(params) {
45
+ const p = params;
46
+ return handleGetRedditPosts(p.urls, env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET, p.max_comments, {
47
+ fetchComments: p.fetch_comments,
48
+ maxCommentsOverride: p.max_comments !== 100 ? p.max_comments : undefined,
49
+ });
50
+ }
51
+ /**
52
+ * Wrapper for deep_research handler
53
+ */
54
+ async function deepResearchHandler(params) {
55
+ const { content } = await handleDeepResearch(params);
56
+ return content;
57
+ }
58
+ /**
59
+ * Wrapper for scrape_links handler
60
+ */
61
+ async function scrapeLinksHandler(params) {
62
+ const { content } = await handleScrapeLinks(params);
63
+ return content;
64
+ }
65
+ /**
66
+ * Wrapper for web_search handler
67
+ */
68
+ async function webSearchHandler(params) {
69
+ const { content } = await handleWebSearch(params);
70
+ return content;
71
+ }
72
+ // ============================================================================
73
+ // Tool Registry
74
+ // ============================================================================
75
+ /**
76
+ * Central registry of all MCP tools
77
+ */
78
+ export const toolRegistry = {
79
+ search_reddit: {
80
+ name: 'search_reddit',
81
+ capability: 'search',
82
+ schema: searchRedditParamsSchema,
83
+ handler: searchRedditHandler,
84
+ },
85
+ get_reddit_post: {
86
+ name: 'get_reddit_post',
87
+ capability: 'reddit',
88
+ schema: getRedditPostParamsSchema,
89
+ handler: getRedditPostHandler,
90
+ },
91
+ deep_research: {
92
+ name: 'deep_research',
93
+ capability: 'deepResearch',
94
+ schema: deepResearchParamsSchema,
95
+ handler: deepResearchHandler,
96
+ transformResponse: (result) => ({
97
+ content: result,
98
+ isError: result.includes('# āŒ Error'),
99
+ }),
100
+ },
101
+ scrape_links: {
102
+ name: 'scrape_links',
103
+ capability: 'scraping',
104
+ schema: scrapeLinksParamsSchema,
105
+ handler: scrapeLinksHandler,
106
+ transformResponse: (result) => ({
107
+ content: result,
108
+ isError: result.includes('# āŒ Scraping Failed'),
109
+ }),
110
+ },
111
+ web_search: {
112
+ name: 'web_search',
113
+ capability: 'search',
114
+ schema: webSearchParamsSchema,
115
+ handler: webSearchHandler,
116
+ transformResponse: (result) => ({
117
+ content: result,
118
+ isError: result.includes('# āŒ web_search'),
119
+ }),
120
+ },
121
+ };
122
+ // ============================================================================
123
+ // Execute Tool (Main Entry Point)
124
+ // ============================================================================
125
+ /**
126
+ * Execute a tool by name with full middleware chain
127
+ *
128
+ * Middleware steps:
129
+ * 1. Lookup tool in registry (throw McpError if not found)
130
+ * 2. Check capability (return error response if missing)
131
+ * 3. Validate params with Zod (return error response if invalid)
132
+ * 4. Execute handler (catch and format any errors)
133
+ * 5. Transform response if needed
134
+ *
135
+ * @param name - Tool name from request
136
+ * @param args - Raw arguments from request
137
+ * @param capabilities - Current capabilities from getCapabilities()
138
+ * @returns MCP-compliant tool result
139
+ */
140
+ export async function executeTool(name, args, capabilities) {
141
+ // Step 1: Lookup tool
142
+ const tool = toolRegistry[name];
143
+ if (!tool) {
144
+ throw new McpError(McpErrorCode.MethodNotFound, `Method not found: ${name}. Available tools: ${Object.keys(toolRegistry).join(', ')}`);
145
+ }
146
+ // Step 2: Check capability
147
+ if (tool.capability && !capabilities[tool.capability]) {
148
+ return {
149
+ content: [{ type: 'text', text: getMissingEnvMessage(tool.capability) }],
150
+ isError: true,
151
+ };
152
+ }
153
+ // Step 3: Validate params with Zod
154
+ let validatedParams;
155
+ try {
156
+ validatedParams = tool.schema.parse(args);
157
+ }
158
+ catch (error) {
159
+ if (error instanceof ZodError) {
160
+ const issues = error.issues
161
+ .map((i) => `- **${i.path.join('.') || 'root'}**: ${i.message}`)
162
+ .join('\n');
163
+ return {
164
+ content: [{ type: 'text', text: `# āŒ Validation Error\n\n${issues}` }],
165
+ isError: true,
166
+ };
167
+ }
168
+ // Non-Zod validation error
169
+ const structured = classifyError(error);
170
+ return createToolErrorFromStructured(structured);
171
+ }
172
+ // Step 3.5: Optional post-validation
173
+ if (tool.postValidate) {
174
+ const postError = tool.postValidate(validatedParams);
175
+ if (postError) {
176
+ return {
177
+ content: [{ type: 'text', text: `# āŒ Validation Error\n\n${postError}` }],
178
+ isError: true,
179
+ };
180
+ }
181
+ }
182
+ // Step 4: Execute handler
183
+ let result;
184
+ try {
185
+ result = await tool.handler(validatedParams);
186
+ }
187
+ catch (error) {
188
+ // Handler threw (shouldn't happen if handlers follow "never throw" pattern)
189
+ const structured = classifyError(error);
190
+ return createToolErrorFromStructured(structured);
191
+ }
192
+ // Step 5: Transform response
193
+ if (tool.transformResponse) {
194
+ const transformed = tool.transformResponse(result);
195
+ return {
196
+ content: [{ type: 'text', text: transformed.content }],
197
+ isError: transformed.isError,
198
+ };
199
+ }
200
+ // Default: success response
201
+ return {
202
+ content: [{ type: 'text', text: result }],
203
+ };
204
+ }
205
+ // ============================================================================
206
+ // Utility Functions
207
+ // ============================================================================
208
+ /**
209
+ * Get list of all registered tool names
210
+ */
211
+ export function getRegisteredToolNames() {
212
+ return Object.keys(toolRegistry);
213
+ }
214
+ /**
215
+ * Check if a tool is registered
216
+ */
217
+ export function isToolRegistered(name) {
218
+ return name in toolRegistry;
219
+ }
220
+ /**
221
+ * Get tool capabilities for logging
222
+ */
223
+ export function getToolCapabilities() {
224
+ const caps = getCapabilities();
225
+ const enabled = [];
226
+ const disabled = [];
227
+ for (const [name, tool] of Object.entries(toolRegistry)) {
228
+ const capKey = tool.capability;
229
+ if (!capKey || caps[capKey]) {
230
+ enabled.push(name);
231
+ }
232
+ else {
233
+ disabled.push(name);
234
+ }
235
+ }
236
+ return { enabled, disabled };
237
+ }
238
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEzF,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,oBAAoB,EAAqB,MAAM,oBAAoB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAElF,iBAAiB;AACjB,OAAO,EAAE,wBAAwB,EAA2B,MAAM,6BAA6B,CAAC;AAChG,OAAO,EAAE,uBAAuB,EAA0B,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,qBAAqB,EAAwB,MAAM,0BAA0B,CAAC;AAEvF,kBAAkB;AAClB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAgC9C,+EAA+E;AAC/E,gDAAgD;AAChD,+EAA+E;AAE/E,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACxC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;CACtC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;AAEvB;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,MAAe;IAChD,MAAM,CAAC,GAAG,MAAkD,CAAC;IAC7D,OAAO,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,cAAe,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,oBAAoB,CAAC,MAAe;IACjD,MAAM,CAAC,GAAG,MAAmD,CAAC;IAC9D,OAAO,oBAAoB,CACzB,CAAC,CAAC,IAAI,EACN,GAAG,CAAC,gBAAiB,EACrB,GAAG,CAAC,oBAAqB,EACzB,CAAC,CAAC,YAAY,EACd;QACE,aAAa,EAAE,CAAC,CAAC,cAAc;QAC/B,mBAAmB,EAAE,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KACzE,CACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,MAAe;IAChD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,kBAAkB,CAAC,MAA4B,CAAC,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAAC,MAAe;IAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,iBAAiB,CAAC,MAA2B,CAAC,CAAC;IACzE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,gBAAgB,CAAC,MAAe;IAC7C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC,MAAyB,CAAC,CAAC;IACrE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,aAAa,EAAE;QACb,IAAI,EAAE,eAAe;QACrB,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,wBAAwB;QAChC,OAAO,EAAE,mBAAmB;KAC7B;IAED,eAAe,EAAE;QACf,IAAI,EAAE,iBAAiB;QACvB,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,yBAAyB;QACjC,OAAO,EAAE,oBAAoB;KAC9B;IAED,aAAa,EAAE;QACb,IAAI,EAAE,eAAe;QACrB,UAAU,EAAE,cAAc;QAC1B,MAAM,EAAE,wBAAwB;QAChC,OAAO,EAAE,mBAAmB;QAC5B,iBAAiB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;SACtC,CAAC;KACH;IAED,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,UAAU,EAAE,UAAU;QACtB,MAAM,EAAE,uBAAuB;QAC/B,OAAO,EAAE,kBAAkB;QAC3B,iBAAiB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC;SAChD,CAAC;KACH;IAED,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,qBAAqB;QAC7B,OAAO,EAAE,gBAAgB;QACzB,iBAAiB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC;SAC3C,CAAC;KACH;CACF,CAAC;AAEF,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,IAAa,EACb,YAA0B;IAE1B,sBAAsB;IACtB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,QAAQ,CAChB,YAAY,CAAC,cAAc,EAC3B,qBAAqB,IAAI,sBAAsB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACtF,CAAC;IACJ,CAAC;IAED,2BAA2B;IAC3B,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACxE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,mCAAmC;IACnC,IAAI,eAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;iBACxB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC/D,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,MAAM,EAAE,EAAE,CAAC;gBACtE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,2BAA2B;QAC3B,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,OAAO,6BAA6B,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC;IAED,qCAAqC;IACrC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;QACrD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,SAAS,EAAE,EAAE,CAAC;gBACzE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4EAA4E;QAC5E,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,OAAO,6BAA6B,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC;IAED,6BAA6B;IAC7B,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACnD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;YACtD,OAAO,EAAE,WAAW,CAAC,OAAO;SAC7B,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,IAAI,YAAY,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC/B,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shared Tool Utilities
3
+ * Extracted from individual handlers to eliminate duplication
4
+ */
5
+ /**
6
+ * Logger function type used by tools
7
+ */
8
+ export type ToolLogger = (level: 'info' | 'error' | 'debug', message: string, sessionId: string) => Promise<void>;
9
+ /**
10
+ * Standard tool options passed to handlers
11
+ */
12
+ export interface ToolOptions {
13
+ sessionId?: string;
14
+ logger?: ToolLogger;
15
+ }
16
+ /**
17
+ * Safe logger wrapper - NEVER throws
18
+ * Logs to provided logger or falls back to console.error
19
+ *
20
+ * @param logger - Optional logger function
21
+ * @param sessionId - Session ID for logging context
22
+ * @param level - Log level
23
+ * @param message - Message to log
24
+ * @param toolName - Name of the tool for prefixing
25
+ */
26
+ export declare function safeLog(logger: ToolLogger | undefined, sessionId: string | undefined, level: 'info' | 'error' | 'debug', message: string, toolName: string): Promise<void>;
27
+ /**
28
+ * Calculate token allocation for batch operations
29
+ * Distributes a fixed budget across multiple items
30
+ *
31
+ * @param count - Number of items to distribute budget across
32
+ * @param budget - Total token budget
33
+ * @returns Tokens per item
34
+ */
35
+ export declare function calculateTokenAllocation(count: number, budget: number): number;
36
+ /**
37
+ * Format retry hint based on error retryability
38
+ *
39
+ * @param retryable - Whether the error is retryable
40
+ * @returns Hint string or empty string
41
+ */
42
+ export declare function formatRetryHint(retryable: boolean): string;
43
+ /**
44
+ * Create a standard error markdown response
45
+ *
46
+ * @param toolName - Name of the tool that errored
47
+ * @param errorCode - Error code
48
+ * @param message - Error message
49
+ * @param retryable - Whether error is retryable
50
+ * @param tip - Optional tip for resolution
51
+ * @returns Formatted markdown error string
52
+ */
53
+ export declare function formatToolError(toolName: string, errorCode: string, message: string, retryable: boolean, tip?: string): string;
54
+ /**
55
+ * Validate that a value is a non-empty array
56
+ *
57
+ * @param value - Value to check
58
+ * @param fieldName - Field name for error message
59
+ * @returns Error message or undefined if valid
60
+ */
61
+ export declare function validateNonEmptyArray(value: unknown, fieldName: string): string | undefined;
62
+ /**
63
+ * Validate array length is within bounds
64
+ *
65
+ * @param arr - Array to check
66
+ * @param min - Minimum length
67
+ * @param max - Maximum length
68
+ * @param fieldName - Field name for error message
69
+ * @returns Error message or undefined if valid
70
+ */
71
+ export declare function validateArrayBounds(arr: unknown[], min: number, max: number, fieldName: string): string | undefined;
72
+ /**
73
+ * Build standard header for batch operation results
74
+ *
75
+ * @param title - Title of the results section
76
+ * @param count - Number of items processed
77
+ * @param tokensPerItem - Tokens allocated per item
78
+ * @param totalBudget - Total token budget
79
+ * @returns Formatted header string
80
+ */
81
+ export declare function buildBatchHeader(title: string, count: number, tokensPerItem: number, totalBudget: number): string;
82
+ /**
83
+ * Build status line for batch results
84
+ *
85
+ * @param successful - Number of successful items
86
+ * @param failed - Number of failed items
87
+ * @param batches - Number of batches processed
88
+ * @param extras - Optional extra status items
89
+ * @returns Formatted status line
90
+ */
91
+ export declare function buildStatusLine(successful: number, failed: number, batches: number, extras?: string[]): string;
92
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/tools/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CACvB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,KACd,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAMD;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAQf;AAMD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG9E;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAI1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,OAAO,EAClB,GAAG,CAAC,EAAE,MAAM,GACX,MAAM,CAIR;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAQpB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,MAAM,CAMR"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Shared Tool Utilities
3
+ * Extracted from individual handlers to eliminate duplication
4
+ */
5
+ // ============================================================================
6
+ // Logging Utilities
7
+ // ============================================================================
8
+ /**
9
+ * Safe logger wrapper - NEVER throws
10
+ * Logs to provided logger or falls back to console.error
11
+ *
12
+ * @param logger - Optional logger function
13
+ * @param sessionId - Session ID for logging context
14
+ * @param level - Log level
15
+ * @param message - Message to log
16
+ * @param toolName - Name of the tool for prefixing
17
+ */
18
+ export async function safeLog(logger, sessionId, level, message, toolName) {
19
+ if (!logger || !sessionId)
20
+ return;
21
+ try {
22
+ await logger(level, `[${toolName}] ${message}`, sessionId);
23
+ }
24
+ catch {
25
+ // Silently ignore logger errors - they should never crash the tool
26
+ console.error(`[${toolName}] Logger failed: ${message}`);
27
+ }
28
+ }
29
+ // ============================================================================
30
+ // Token Allocation
31
+ // ============================================================================
32
+ /**
33
+ * Calculate token allocation for batch operations
34
+ * Distributes a fixed budget across multiple items
35
+ *
36
+ * @param count - Number of items to distribute budget across
37
+ * @param budget - Total token budget
38
+ * @returns Tokens per item
39
+ */
40
+ export function calculateTokenAllocation(count, budget) {
41
+ if (count <= 0)
42
+ return budget;
43
+ return Math.floor(budget / count);
44
+ }
45
+ // ============================================================================
46
+ // Error Formatting
47
+ // ============================================================================
48
+ /**
49
+ * Format retry hint based on error retryability
50
+ *
51
+ * @param retryable - Whether the error is retryable
52
+ * @returns Hint string or empty string
53
+ */
54
+ export function formatRetryHint(retryable) {
55
+ return retryable
56
+ ? '\n\nšŸ’” This error may be temporary. Try again in a moment.'
57
+ : '';
58
+ }
59
+ /**
60
+ * Create a standard error markdown response
61
+ *
62
+ * @param toolName - Name of the tool that errored
63
+ * @param errorCode - Error code
64
+ * @param message - Error message
65
+ * @param retryable - Whether error is retryable
66
+ * @param tip - Optional tip for resolution
67
+ * @returns Formatted markdown error string
68
+ */
69
+ export function formatToolError(toolName, errorCode, message, retryable, tip) {
70
+ const retryHint = formatRetryHint(retryable);
71
+ const tipSection = tip ? `\n\n**Tip:** ${tip}` : '';
72
+ return `# āŒ ${toolName}: Operation Failed\n\n**${errorCode}:** ${message}${retryHint}${tipSection}`;
73
+ }
74
+ // ============================================================================
75
+ // Validation Helpers
76
+ // ============================================================================
77
+ /**
78
+ * Validate that a value is a non-empty array
79
+ *
80
+ * @param value - Value to check
81
+ * @param fieldName - Field name for error message
82
+ * @returns Error message or undefined if valid
83
+ */
84
+ export function validateNonEmptyArray(value, fieldName) {
85
+ if (!Array.isArray(value)) {
86
+ return `${fieldName} must be an array`;
87
+ }
88
+ if (value.length === 0) {
89
+ return `${fieldName} must not be empty`;
90
+ }
91
+ return undefined;
92
+ }
93
+ /**
94
+ * Validate array length is within bounds
95
+ *
96
+ * @param arr - Array to check
97
+ * @param min - Minimum length
98
+ * @param max - Maximum length
99
+ * @param fieldName - Field name for error message
100
+ * @returns Error message or undefined if valid
101
+ */
102
+ export function validateArrayBounds(arr, min, max, fieldName) {
103
+ if (arr.length < min) {
104
+ return `${fieldName} requires at least ${min} items. Received: ${arr.length}`;
105
+ }
106
+ if (arr.length > max) {
107
+ return `${fieldName} allows at most ${max} items. Received: ${arr.length}. Please remove ${arr.length - max} item(s).`;
108
+ }
109
+ return undefined;
110
+ }
111
+ // ============================================================================
112
+ // Response Builders
113
+ // ============================================================================
114
+ /**
115
+ * Build standard header for batch operation results
116
+ *
117
+ * @param title - Title of the results section
118
+ * @param count - Number of items processed
119
+ * @param tokensPerItem - Tokens allocated per item
120
+ * @param totalBudget - Total token budget
121
+ * @returns Formatted header string
122
+ */
123
+ export function buildBatchHeader(title, count, tokensPerItem, totalBudget) {
124
+ return `# ${title} (${count} items)\n\n**Token Allocation:** ${tokensPerItem.toLocaleString()} tokens/item (${count} items, ${totalBudget.toLocaleString()} total budget)`;
125
+ }
126
+ /**
127
+ * Build status line for batch results
128
+ *
129
+ * @param successful - Number of successful items
130
+ * @param failed - Number of failed items
131
+ * @param batches - Number of batches processed
132
+ * @param extras - Optional extra status items
133
+ * @returns Formatted status line
134
+ */
135
+ export function buildStatusLine(successful, failed, batches, extras) {
136
+ let status = `**Status:** āœ… ${successful} successful | āŒ ${failed} failed | šŸ“¦ ${batches} batch(es)`;
137
+ if (extras && extras.length > 0) {
138
+ status += ` | ${extras.join(' | ')}`;
139
+ }
140
+ return status;
141
+ }
142
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tools/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "research-powerpack-mcp",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
4
4
  "description": "The ultimate research MCP toolkit: Reddit mining, web search with CTR aggregation, AI-powered deep research, and intelligent web scraping - all in one modular package",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@
11
11
  "dist"
12
12
  ],
13
13
  "scripts": {
14
- "build": "tsc",
14
+ "build": "tsc && cp -r src/config/yaml dist/config/",
15
15
  "prepublishOnly": "npm run build",
16
16
  "dev": "tsx src/index.ts",
17
17
  "start": "node dist/index.js",
@@ -49,6 +49,7 @@
49
49
  "@modelcontextprotocol/sdk": "^1.18.1",
50
50
  "openai": "^4.77.0",
51
51
  "turndown": "^7.2.2",
52
+ "yaml": "^2.7.0",
52
53
  "zod": "^3.24.1",
53
54
  "zod-to-json-schema": "^3.24.1"
54
55
  },