@oevortex/ddg_search 1.1.2 → 1.1.3

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/Dockerfile CHANGED
@@ -1,20 +1,26 @@
1
- # Use official Node.js LTS image
2
- FROM node:20-alpine
3
-
4
- # Set working directory
1
+ # Build stage: install dependencies
2
+ FROM node:22-slim AS build
5
3
  WORKDIR /app
6
4
 
7
- # Copy package.json and package-lock.json
8
- COPY package*.json ./
5
+ # Copy package manifests and lockfile first for better caching
6
+ COPY package.json package-lock.json ./
9
7
 
10
- # Install dependencies
11
- RUN npm install --production
8
+ # Install production dependencies (use npm ci when lockfile exists)
9
+ RUN if [ -f package-lock.json ]; then npm ci --production; else npm install --production; fi
12
10
 
13
- # Copy source code
11
+ # Copy application source
14
12
  COPY . .
15
13
 
16
- # Expose port (if your app listens on a port, e.g. 3000)
14
+ # Final minimal runtime image
15
+ FROM node:22-slim AS runtime
16
+ WORKDIR /app
17
+
18
+ # Copy node_modules and built app from build stage
19
+ COPY --from=build /app/node_modules ./node_modules
20
+ COPY --from=build /app .
21
+
22
+ # Expose port in case the MCP server needs it
17
23
  EXPOSE 3000
18
24
 
19
- # Default command (adjust if your entry point is different)
20
- CMD ["node", "src/index.js"]
25
+ # Default command: use the CLI entry which starts the MCP server
26
+ CMD ["node", "bin/cli.js"]
package/README.md CHANGED
@@ -227,4 +227,4 @@ Apache License 2.0
227
227
 
228
228
  <div align="center">
229
229
  <sub>Made with ❤️ by <a href="https://youtube.com/@OEvortex">@OEvortex</a></sub>
230
- </div>
230
+ </div>
package/bin/cli.js CHANGED
@@ -19,29 +19,47 @@ async function startServer() {
19
19
  id: 'ddg-search-mcp',
20
20
  name: 'DuckDuckGo & Felo AI Search MCP',
21
21
  description: 'A Model Context Protocol server for web search using DuckDuckGo and Felo AI',
22
- version: '1.1.1'
22
+ version: '1.1.2'
23
23
  }, {
24
24
  capabilities: {
25
- tools: {}
25
+ tools: {
26
+ listChanged: true
27
+ }
26
28
  }
27
29
  });
28
30
 
31
+ // Global variable to track available tools
32
+ let availableTools = [
33
+ searchToolDefinition,
34
+ fetchUrlToolDefinition,
35
+ metadataToolDefinition,
36
+ feloToolDefinition
37
+ ];
38
+
29
39
  // Define available tools
30
40
  server.setRequestHandler(ListToolsRequestSchema, async () => {
31
41
  return {
32
- tools: [
33
- searchToolDefinition,
34
- fetchUrlToolDefinition,
35
- metadataToolDefinition,
36
- feloToolDefinition
37
- ]
42
+ tools: availableTools
38
43
  };
39
44
  });
40
45
 
46
+ // Function to notify clients when tools list changes
47
+ function notifyToolsChanged() {
48
+ server.notification({
49
+ method: 'notifications/tools/list_changed'
50
+ });
51
+ }
52
+
41
53
  // Handle tool execution
42
54
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
43
55
  try {
44
56
  const { name, arguments: args } = request.params;
57
+
58
+ // Validate tool name
59
+ const validTools = ['web-search', 'fetch-url', 'url-metadata', 'felo-search'];
60
+ if (!validTools.includes(name)) {
61
+ throw new Error(`Unknown tool: ${name}`);
62
+ }
45
63
 
46
64
  // Route to the appropriate tool handler
47
65
  switch (name) {
@@ -61,13 +79,15 @@ async function startServer() {
61
79
  throw new Error(`Tool not found: ${name}`);
62
80
  }
63
81
  } catch (error) {
64
- console.error(`Error handling ${request.params.name} request:`, error);
82
+ console.error(`Error handling ${request.params.name} tool call:`, error);
83
+
84
+ // Return proper tool execution error format
65
85
  return {
66
86
  isError: true,
67
87
  content: [
68
88
  {
69
89
  type: 'text',
70
- text: `Error: ${error.message}`
90
+ text: `Error executing tool '${request.params.name}': ${error.message}`
71
91
  }
72
92
  ]
73
93
  };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@oevortex/ddg_search","version":"1.1.2","description":"A Model Context Protocol server for web search using DuckDuckGo and Felo AI","main":"src/index.js","bin":{"ddg-search-mcp":"bin/cli.js","oevortex-ddg-search":"bin/cli.js"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","start":"node bin/cli.js","prepublishOnly":"npm run lint","lint":"echo \"No linting configured\""},"publishConfig":{"access":"public"},"keywords":["mcp","model-context-protocol","duckduckgo","felo","search","web-search","ai-search","claude","ai","llm"],"author":"OEvortex","license":"Apache-2.0","type":"module","dependencies":{"@modelcontextprotocol/sdk":"^1.9.0","axios":"^1.8.4","cheerio":"^1.0.0","jsdom":"^26.1.0","uuid":"^9.0.1"}}
1
+ {"name":"@oevortex/ddg_search","version":"1.1.3","description":"A Model Context Protocol server for web search using DuckDuckGo and Felo AI","main":"src/index.js","module":"src/index.ts","exports":{".":{"import":"./src/index.js","default":"./src/index.js"}},"bin":{"ddg-search-mcp":"bin/cli.js","oevortex-ddg-search":"bin/cli.js"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","start":"node bin/cli.js","prepublishOnly":"npm run lint","lint":"echo \"No linting configured\"","build":"npx @smithery/cli build","dev":"npx @smithery/cli dev"},"publishConfig":{"access":"public"},"keywords":["mcp","model-context-protocol","duckduckgo","felo","search","web-search","ai-search","claude","ai","llm"],"author":"OEvortex","license":"Apache-2.0","type":"module","dependencies":{"@modelcontextprotocol/sdk":"^1.17.4","axios":"^1.8.4","cheerio":"^1.0.0","jsdom":"^26.1.0","smithery":"^0.5.2","uuid":"^9.0.1"},"devDependencies":{"@types/node":"^24.3.0","tsx":"^4.20.4","typescript":"^5.9.2"}}
package/smithery.yaml ADDED
@@ -0,0 +1,12 @@
1
+ runtime: typescript
2
+
3
+ build:
4
+ external:
5
+ - canvas
6
+ - utf-8-validate
7
+ - bufferutil
8
+ esbuild:
9
+ bundle: true
10
+ platform: node
11
+ format: cjs
12
+ target: node18
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
2
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
3
 
5
4
  // Import tool definitions and handlers
@@ -8,75 +7,99 @@ import { fetchUrlToolDefinition, fetchUrlToolHandler } from './tools/fetchUrlToo
8
7
  import { metadataToolDefinition, metadataToolHandler } from './tools/metadataTool.js';
9
8
  import { feloToolDefinition, feloToolHandler } from './tools/feloTool.js';
10
9
 
11
- // Create the MCP server
12
- const server = new Server({
13
- id: 'ddg-search-mcp',
14
- name: 'DuckDuckGo & Felo AI Search MCP',
15
- description: 'A Model Context Protocol server for web search using DuckDuckGo and Felo AI',
16
- version: '1.1.1'
17
- }, {
18
- capabilities: {
19
- tools: {}
20
- }
21
- });
10
+ // Required: Export default createServer function for Smithery
11
+ export default function createServer({ config } = {}) {
12
+ console.log('Creating MCP server with latest SDK...');
13
+
14
+ // Global variable to track available tools
15
+ const availableTools = [
16
+ searchToolDefinition,
17
+ fetchUrlToolDefinition,
18
+ metadataToolDefinition,
19
+ feloToolDefinition
20
+ ];
21
+
22
+ console.log('Available tools:', availableTools.map(t => t.name));
22
23
 
23
- // Define available tools
24
- server.setRequestHandler(ListToolsRequestSchema, async () => {
25
- return {
26
- tools: [
27
- searchToolDefinition,
28
- fetchUrlToolDefinition,
29
- metadataToolDefinition,
30
- feloToolDefinition
31
- ]
32
- };
33
- });
24
+ // Create the MCP server using the Server class
25
+ const server = new Server({
26
+ name: 'ddg-search-mcp',
27
+ version: '1.1.2'
28
+ }, {
29
+ capabilities: {
30
+ tools: {
31
+ listChanged: true
32
+ }
33
+ }
34
+ });
34
35
 
35
- // Handle tool execution
36
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
37
- try {
38
- const { name, arguments: args } = request.params;
39
- // Route to the appropriate tool handler
40
- switch (name) {
41
- case 'web-search':
42
- return await searchToolHandler(args);
43
-
44
- case 'fetch-url':
45
- return await fetchUrlToolHandler(args);
46
-
47
- case 'url-metadata':
48
- return await metadataToolHandler(args);
36
+ // Define available tools
37
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
38
+ console.log('Tools list requested, returning:', availableTools.length, 'tools');
39
+ return {
40
+ tools: availableTools
41
+ };
42
+ });
43
+
44
+ // Handle tool execution
45
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
46
+ try {
47
+ const { name, arguments: args } = request.params;
48
+ console.log(`Tool call received: ${name} with args:`, args);
49
49
 
50
- case 'felo-search':
51
- return await feloToolHandler(args);
50
+ // Route to the appropriate tool handler
51
+ switch (name) {
52
+ case 'web-search':
53
+ return await searchToolHandler(args);
54
+
55
+ case 'fetch-url':
56
+ return await fetchUrlToolHandler(args);
57
+
58
+ case 'url-metadata':
59
+ return await metadataToolHandler(args);
60
+
61
+ case 'felo-search':
62
+ return await feloToolHandler(args);
63
+
64
+ default:
65
+ throw new Error(`Tool not found: ${name}`);
66
+ }
67
+ } catch (error) {
68
+ console.error(`Error handling ${request.params.name} tool call:`, error);
52
69
 
53
- default:
54
- throw new Error(`Tool not found: ${name}`);
70
+ // Return proper tool execution error format
71
+ return {
72
+ isError: true,
73
+ content: [
74
+ {
75
+ type: 'text',
76
+ text: `Error executing tool '${request.params.name}': ${error.message}`
77
+ }
78
+ ]
79
+ };
55
80
  }
56
- } catch (error) {
57
- console.error(`Error handling ${request.params.name} request:`, error);
58
- return {
59
- isError: true,
60
- content: [
61
- {
62
- type: 'text',
63
- text: `Error: ${error.message}`
64
- }
65
- ]
66
- };
67
- }
68
- });
81
+ });
69
82
 
70
- // Start the server with stdio transport
71
- async function main() {
72
- try {
73
- const transport = new StdioServerTransport();
74
- await server.connect(transport);
75
- console.error('WebSearch MCP server started and listening on stdio');
76
- } catch (error) {
77
- console.error('Failed to start server:', error);
78
- process.exit(1);
79
- }
83
+ console.log('MCP server created successfully');
84
+
85
+ // Return the server instance (required for Smithery)
86
+ return server;
80
87
  }
81
88
 
82
- main();
89
+ // Legacy standalone server support (for CLI usage)
90
+ if (import.meta.url === `file://${process.argv[1]}`) {
91
+ async function main() {
92
+ try {
93
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
94
+ const server = createServer();
95
+ const transport = new StdioServerTransport();
96
+ await server.connect(transport);
97
+ console.error('WebSearch MCP server started and listening on stdio');
98
+ } catch (error) {
99
+ console.error('Failed to start server:', error);
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ main();
105
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
+
4
+ // Import tool definitions and handlers
5
+ import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
6
+ import { fetchUrlToolDefinition, fetchUrlToolHandler } from './tools/fetchUrlTool.js';
7
+ import { metadataToolDefinition, metadataToolHandler } from './tools/metadataTool.js';
8
+ import { feloToolDefinition, feloToolHandler } from './tools/feloTool.js';
9
+
10
+ // Required: Export default createServer function for Smithery
11
+ export default function createServer({ config }: { config?: any } = {}) {
12
+ console.log('Creating MCP server with latest SDK...');
13
+
14
+ // Global variable to track available tools
15
+ const availableTools = [
16
+ searchToolDefinition,
17
+ fetchUrlToolDefinition,
18
+ metadataToolDefinition,
19
+ feloToolDefinition
20
+ ];
21
+
22
+ console.log('Available tools:', availableTools.map(t => t.name));
23
+
24
+ // Create the MCP server using the Server class
25
+ const server = new Server({
26
+ name: 'ddg-search-mcp',
27
+ version: '1.1.2'
28
+ }, {
29
+ capabilities: {
30
+ tools: {
31
+ listChanged: true
32
+ }
33
+ }
34
+ });
35
+
36
+ // Define available tools
37
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
38
+ console.log('Tools list requested, returning:', availableTools.length, 'tools');
39
+ return {
40
+ tools: availableTools
41
+ };
42
+ });
43
+
44
+ // Handle tool execution
45
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
46
+ try {
47
+ const { name, arguments: args } = request.params;
48
+ console.log(`Tool call received: ${name} with args:`, args);
49
+
50
+ // Route to the appropriate tool handler
51
+ switch (name) {
52
+ case 'web-search':
53
+ return await searchToolHandler(args);
54
+
55
+ case 'fetch-url':
56
+ return await fetchUrlToolHandler(args);
57
+
58
+ case 'url-metadata':
59
+ return await metadataToolHandler(args);
60
+
61
+ case 'felo-search':
62
+ return await feloToolHandler(args);
63
+
64
+ default:
65
+ throw new Error(`Tool not found: ${name}`);
66
+ }
67
+ } catch (error: any) {
68
+ console.error(`Error handling ${request.params.name} tool call:`, error);
69
+
70
+ // Return proper tool execution error format
71
+ return {
72
+ isError: true,
73
+ content: [
74
+ {
75
+ type: 'text',
76
+ text: `Error executing tool '${request.params.name}': ${error.message}`
77
+ }
78
+ ]
79
+ };
80
+ }
81
+ });
82
+
83
+ console.log('MCP server created successfully');
84
+
85
+ // Return the server instance (required for Smithery)
86
+ return server;
87
+ }
88
+
89
+ // Optional: No configuration schema needed for this server
90
+ // export const configSchema = z.object({});
@@ -5,7 +5,8 @@ import { searchFelo } from '../utils/search_felo.js';
5
5
  */
6
6
  export const feloToolDefinition = {
7
7
  name: 'felo-search',
8
- description: 'Advanced web search tool for technical intelligence. Retrieves up-to-date information from the web, including latest software releases, security advisories, migration guides, benchmarks, developer documentation, and community insights. Designed for function calling and automation, it provides structured, relevant results for engineering, DevOps, and research workflows. Supports both standard and streaming responses for real-time data consumption.',
8
+ title: 'Felo AI Advanced Search',
9
+ description: 'Advanced AI-powered web search for technical intelligence. Retrieves up-to-date information including software releases, security advisories, migration guides, benchmarks, developer documentation, and community insights. Supports both standard and streaming responses.',
9
10
  inputSchema: {
10
11
  type: 'object',
11
12
  properties: {
@@ -22,9 +23,8 @@ export const feloToolDefinition = {
22
23
  required: ['query']
23
24
  },
24
25
  annotations: {
25
- title: 'Felo AI Advanced Web Search',
26
- readOnlyHint: 'Results are read-only and cannot be modified. Use for information retrieval only.',
27
- openWorldHint: 'Searches the open web and technical sources for the most current and relevant data. Not limited to a fixed dataset.'
26
+ readOnlyHint: true,
27
+ openWorldHint: false
28
28
  }
29
29
  };
30
30
 
@@ -5,50 +5,46 @@ import { fetchUrlContent } from '../utils/search.js';
5
5
  */
6
6
  export const fetchUrlToolDefinition = {
7
7
  name: 'fetch-url',
8
- description: 'Fetch the content of a URL and return it as text, with options to control extraction',
8
+ title: 'Fetch URL Content',
9
+ description: 'Fetch and extract the main content from any URL, with customizable extraction options for text, links, and images',
9
10
  inputSchema: {
10
11
  type: 'object',
11
12
  properties: {
12
13
  url: {
13
14
  type: 'string',
14
- description: 'The URL to fetch'
15
+ description: 'The URL to fetch content from (must be a valid HTTP/HTTPS URL)'
15
16
  },
16
17
  maxLength: {
17
18
  type: 'integer',
18
- description: 'Maximum length of content to return (default: 10000)',
19
+ description: 'Maximum length of content to return in characters (default: 10000)',
19
20
  default: 10000,
20
21
  minimum: 1000,
21
22
  maximum: 50000
22
23
  },
23
24
  extractMainContent: {
24
25
  type: 'boolean',
25
- description: 'Whether to attempt to extract main content (default: true)',
26
+ description: 'Whether to attempt to extract main content only, filtering out navigation and ads (default: true)',
26
27
  default: true
27
28
  },
28
29
  includeLinks: {
29
30
  type: 'boolean',
30
- description: 'Whether to include link text (default: true)',
31
+ description: 'Whether to include link text in the extracted content (default: true)',
31
32
  default: true
32
33
  },
33
34
  includeImages: {
34
35
  type: 'boolean',
35
- description: 'Whether to include image alt text (default: true)',
36
+ description: 'Whether to include image alt text in the extracted content (default: true)',
36
37
  default: true
37
38
  },
38
39
  excludeTags: {
39
40
  type: 'array',
40
- description: 'Tags to exclude from extraction (default: script, style, etc.)',
41
+ description: 'HTML tags to exclude from extraction (default: script, style, etc.)',
41
42
  items: {
42
43
  type: 'string'
43
44
  }
44
45
  }
45
46
  },
46
47
  required: ['url']
47
- },
48
- annotations: {
49
- title: 'Fetch URL Content',
50
- readOnlyHint: true,
51
- openWorldHint: true
52
48
  }
53
49
  };
54
50
 
@@ -5,21 +5,17 @@ import { extractUrlMetadata } from '../utils/search.js';
5
5
  */
6
6
  export const metadataToolDefinition = {
7
7
  name: 'url-metadata',
8
- description: 'Extract metadata from a URL (title, description, etc.)',
8
+ title: 'URL Metadata Extractor',
9
+ description: 'Extract metadata from a URL including title, description, Open Graph data, and favicon information',
9
10
  inputSchema: {
10
11
  type: 'object',
11
12
  properties: {
12
13
  url: {
13
14
  type: 'string',
14
- description: 'The URL to extract metadata from'
15
+ description: 'The URL to extract metadata from (must be a valid HTTP/HTTPS URL)'
15
16
  }
16
17
  },
17
18
  required: ['url']
18
- },
19
- annotations: {
20
- title: 'URL Metadata',
21
- readOnlyHint: true,
22
- openWorldHint: true
23
19
  }
24
20
  };
25
21
 
@@ -5,34 +5,30 @@ import { searchDuckDuckGo } from '../utils/search.js';
5
5
  */
6
6
  export const searchToolDefinition = {
7
7
  name: 'web-search',
8
- description: 'Search the web using DuckDuckGo and return results',
8
+ title: 'Web Search',
9
+ description: 'Search the web using DuckDuckGo and return comprehensive results with titles, URLs, and snippets',
9
10
  inputSchema: {
10
11
  type: 'object',
11
12
  properties: {
12
13
  query: {
13
14
  type: 'string',
14
- description: 'The search query'
15
+ description: 'The search query to find relevant web pages'
15
16
  },
16
17
  page: {
17
18
  type: 'integer',
18
- description: 'Page number (default: 1)',
19
+ description: 'Page number for pagination (default: 1)',
19
20
  default: 1,
20
21
  minimum: 1
21
22
  },
22
23
  numResults: {
23
24
  type: 'integer',
24
- description: 'Number of results to return (default: 10)',
25
+ description: 'Number of results to return per page (default: 10, max: 20)',
25
26
  default: 10,
26
27
  minimum: 1,
27
28
  maximum: 20
28
29
  }
29
30
  },
30
31
  required: ['query']
31
- },
32
- annotations: {
33
- title: 'Web Search',
34
- readOnlyHint: true,
35
- openWorldHint: true
36
32
  }
37
33
  };
38
34