# 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 |
```