#
tokens: 2908/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Web Search MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server that enables free web searching using Google search results, with no API keys required.
  4 | 
  5 | ## Features
  6 | 
  7 | - Search the web using Google search results
  8 | - No API keys or authentication required
  9 | - Returns structured results with titles, URLs, and descriptions
 10 | - Configurable number of results per search
 11 | 
 12 | ## Installation
 13 | 
 14 | 1. Clone or download this repository
 15 | 2. Install dependencies:
 16 | ```bash
 17 | npm install
 18 | ```
 19 | 3. Build the server:
 20 | ```bash
 21 | npm run build
 22 | ```
 23 | 4. Add the server to your MCP configuration:
 24 | 
 25 | For VSCode (Claude Dev Extension):
 26 | ```json
 27 | {
 28 |   "mcpServers": {
 29 |     "web-search": {
 30 |       "command": "node",
 31 |       "args": ["/path/to/web-search/build/index.js"]
 32 |     }
 33 |   }
 34 | }
 35 | ```
 36 | 
 37 | For Claude Desktop:
 38 | ```json
 39 | {
 40 |   "mcpServers": {
 41 |     "web-search": {
 42 |       "command": "node",
 43 |       "args": ["/path/to/web-search/build/index.js"]
 44 |     }
 45 |   }
 46 | }
 47 | ```
 48 | 
 49 | ## Usage
 50 | 
 51 | The server provides a single tool named `search` that accepts the following parameters:
 52 | 
 53 | ```typescript
 54 | {
 55 |   "query": string,    // The search query
 56 |   "limit": number     // Optional: Number of results to return (default: 5, max: 10)
 57 | }
 58 | ```
 59 | 
 60 | Example usage:
 61 | ```typescript
 62 | use_mcp_tool({
 63 |   server_name: "web-search",
 64 |   tool_name: "search",
 65 |   arguments: {
 66 |     query: "your search query",
 67 |     limit: 3  // optional
 68 |   }
 69 | })
 70 | ```
 71 | 
 72 | Example response:
 73 | ```json
 74 | [
 75 |   {
 76 |     "title": "Example Search Result",
 77 |     "url": "https://example.com",
 78 |     "description": "Description of the search result..."
 79 |   }
 80 | ]
 81 | ```
 82 | 
 83 | ## Limitations
 84 | 
 85 | Since this tool uses web scraping of Google search results, there are some important limitations to be aware of:
 86 | 
 87 | 1. **Rate Limiting**: Google may temporarily block requests if too many searches are performed in a short time. To avoid this:
 88 |    - Keep searches to a reasonable frequency
 89 |    - Use the limit parameter judiciously
 90 |    - Consider implementing delays between searches if needed
 91 | 
 92 | 2. **Result Accuracy**: 
 93 |    - The tool relies on Google's HTML structure, which may change
 94 |    - Some results might be missing descriptions or other metadata
 95 |    - Complex search operators may not work as expected
 96 | 
 97 | 3. **Legal Considerations**:
 98 |    - This tool is intended for personal use
 99 |    - Respect Google's terms of service
100 |    - Consider implementing appropriate rate limiting for your use case
101 | 
102 | ## Contributing
103 | 
104 | Feel free to submit issues and enhancement requests!
105 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "web-search",
 3 |   "version": "0.1.0",
 4 |   "description": "web search the internet",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "web-search": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.6.0",
21 |     "@types/axios": "^0.14.4",
22 |     "@types/cheerio": "^0.22.35",
23 |     "axios": "^1.7.9",
24 |     "cheerio": "^1.0.0"
25 |   },
26 |   "devDependencies": {
27 |     "@types/node": "^20.17.10",
28 |     "typescript": "^5.3.3"
29 |   }
30 | }
31 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | import axios from 'axios';
 11 | import * as cheerio from 'cheerio';
 12 | 
 13 | interface SearchResult {
 14 |   title: string;
 15 |   url: string;
 16 |   description: string;
 17 | }
 18 | 
 19 | const isValidSearchArgs = (args: any): args is { query: string; limit?: number } =>
 20 |   typeof args === 'object' &&
 21 |   args !== null &&
 22 |   typeof args.query === 'string' &&
 23 |   (args.limit === undefined || typeof args.limit === 'number');
 24 | 
 25 | class WebSearchServer {
 26 |   private server: Server;
 27 | 
 28 |   constructor() {
 29 |     this.server = new Server(
 30 |       {
 31 |         name: 'web-search',
 32 |         version: '0.1.0',
 33 |       },
 34 |       {
 35 |         capabilities: {
 36 |           tools: {},
 37 |         },
 38 |       }
 39 |     );
 40 | 
 41 |     this.setupToolHandlers();
 42 |     
 43 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 44 |     process.on('SIGINT', async () => {
 45 |       await this.server.close();
 46 |       process.exit(0);
 47 |     });
 48 |   }
 49 | 
 50 |   private setupToolHandlers() {
 51 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 52 |       tools: [
 53 |         {
 54 |           name: 'search',
 55 |           description: 'Search the web using Google (no API key required)',
 56 |           inputSchema: {
 57 |             type: 'object',
 58 |             properties: {
 59 |               query: {
 60 |                 type: 'string',
 61 |                 description: 'Search query',
 62 |               },
 63 |               limit: {
 64 |                 type: 'number',
 65 |                 description: 'Maximum number of results to return (default: 5)',
 66 |                 minimum: 1,
 67 |                 maximum: 10,
 68 |               },
 69 |             },
 70 |             required: ['query'],
 71 |           },
 72 |         },
 73 |       ],
 74 |     }));
 75 | 
 76 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
 77 |       if (request.params.name !== 'search') {
 78 |         throw new McpError(
 79 |           ErrorCode.MethodNotFound,
 80 |           `Unknown tool: ${request.params.name}`
 81 |         );
 82 |       }
 83 | 
 84 |       if (!isValidSearchArgs(request.params.arguments)) {
 85 |         throw new McpError(
 86 |           ErrorCode.InvalidParams,
 87 |           'Invalid search arguments'
 88 |         );
 89 |       }
 90 | 
 91 |       const query = request.params.arguments.query;
 92 |       const limit = Math.min(request.params.arguments.limit || 5, 10);
 93 | 
 94 |       try {
 95 |         const results = await this.performSearch(query, limit);
 96 |         return {
 97 |           content: [
 98 |             {
 99 |               type: 'text',
100 |               text: JSON.stringify(results, null, 2),
101 |             },
102 |           ],
103 |         };
104 |       } catch (error) {
105 |         if (axios.isAxiosError(error)) {
106 |           return {
107 |             content: [
108 |               {
109 |                 type: 'text',
110 |                 text: `Search error: ${error.message}`,
111 |               },
112 |             ],
113 |             isError: true,
114 |           };
115 |         }
116 |         throw error;
117 |       }
118 |     });
119 |   }
120 | 
121 |   private async performSearch(query: string, limit: number): Promise<SearchResult[]> {
122 |     const response = await axios.get('https://www.google.com/search', {
123 |       params: { q: query },
124 |       headers: {
125 |         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
126 |       }
127 |     });
128 | 
129 |     const $ = cheerio.load(response.data);
130 |     const results: SearchResult[] = [];
131 | 
132 |     $('div.g').each((i, element) => {
133 |       if (i >= limit) return false;
134 | 
135 |       const titleElement = $(element).find('h3');
136 |       const linkElement = $(element).find('a');
137 |       const snippetElement = $(element).find('.VwiC3b');
138 | 
139 |       if (titleElement.length && linkElement.length) {
140 |         const url = linkElement.attr('href');
141 |         if (url && url.startsWith('http')) {
142 |           results.push({
143 |             title: titleElement.text(),
144 |             url: url,
145 |             description: snippetElement.text() || '',
146 |           });
147 |         }
148 |       }
149 |     });
150 | 
151 |     return results;
152 |   }
153 | 
154 |   async run() {
155 |     const transport = new StdioServerTransport();
156 |     await this.server.connect(transport);
157 |     console.error('Web Search MCP server running on stdio');
158 |   }
159 | }
160 | 
161 | const server = new WebSearchServer();
162 | server.run().catch(console.error);
163 | 
```