# Directory Structure
```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
*.log
.env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Web Search MCP Server
A Model Context Protocol (MCP) server that enables free web searching using Google search results, with no API keys required.
## Features
- Search the web using Google search results
- No API keys or authentication required
- Returns structured results with titles, URLs, and descriptions
- Configurable number of results per search
## Installation
1. Clone or download this repository
2. Install dependencies:
```bash
npm install
```
3. Build the server:
```bash
npm run build
```
4. Add the server to your MCP configuration:
For VSCode (Claude Dev Extension):
```json
{
"mcpServers": {
"web-search": {
"command": "node",
"args": ["/path/to/web-search/build/index.js"]
}
}
}
```
For Claude Desktop:
```json
{
"mcpServers": {
"web-search": {
"command": "node",
"args": ["/path/to/web-search/build/index.js"]
}
}
}
```
## Usage
The server provides a single tool named `search` that accepts the following parameters:
```typescript
{
"query": string, // The search query
"limit": number // Optional: Number of results to return (default: 5, max: 10)
}
```
Example usage:
```typescript
use_mcp_tool({
server_name: "web-search",
tool_name: "search",
arguments: {
query: "your search query",
limit: 3 // optional
}
})
```
Example response:
```json
[
{
"title": "Example Search Result",
"url": "https://example.com",
"description": "Description of the search result..."
}
]
```
## Limitations
Since this tool uses web scraping of Google search results, there are some important limitations to be aware of:
1. **Rate Limiting**: Google may temporarily block requests if too many searches are performed in a short time. To avoid this:
- Keep searches to a reasonable frequency
- Use the limit parameter judiciously
- Consider implementing delays between searches if needed
2. **Result Accuracy**:
- The tool relies on Google's HTML structure, which may change
- Some results might be missing descriptions or other metadata
- Complex search operators may not work as expected
3. **Legal Considerations**:
- This tool is intended for personal use
- Respect Google's terms of service
- Consider implementing appropriate rate limiting for your use case
## Contributing
Feel free to submit issues and enhancement requests!
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "web-search",
"version": "0.1.0",
"description": "web search the internet",
"private": true,
"type": "module",
"bin": {
"web-search": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "0.6.0",
"@types/axios": "^0.14.4",
"@types/cheerio": "^0.22.35",
"axios": "^1.7.9",
"cheerio": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.17.10",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import * as cheerio from 'cheerio';
interface SearchResult {
title: string;
url: string;
description: string;
}
const isValidSearchArgs = (args: any): args is { query: string; limit?: number } =>
typeof args === 'object' &&
args !== null &&
typeof args.query === 'string' &&
(args.limit === undefined || typeof args.limit === 'number');
class WebSearchServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'web-search',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search',
description: 'Search the web using Google (no API key required)',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
limit: {
type: 'number',
description: 'Maximum number of results to return (default: 5)',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'search') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!isValidSearchArgs(request.params.arguments)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid search arguments'
);
}
const query = request.params.arguments.query;
const limit = Math.min(request.params.arguments.limit || 5, 10);
try {
const results = await this.performSearch(query, limit);
return {
content: [
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Search error: ${error.message}`,
},
],
isError: true,
};
}
throw error;
}
});
}
private async performSearch(query: string, limit: number): Promise<SearchResult[]> {
const response = await axios.get('https://www.google.com/search', {
params: { q: query },
headers: {
'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'
}
});
const $ = cheerio.load(response.data);
const results: SearchResult[] = [];
$('div.g').each((i, element) => {
if (i >= limit) return false;
const titleElement = $(element).find('h3');
const linkElement = $(element).find('a');
const snippetElement = $(element).find('.VwiC3b');
if (titleElement.length && linkElement.length) {
const url = linkElement.attr('href');
if (url && url.startsWith('http')) {
results.push({
title: titleElement.text(),
url: url,
description: snippetElement.text() || '',
});
}
}
});
return results;
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Web Search MCP server running on stdio');
}
}
const server = new WebSearchServer();
server.run().catch(console.error);
```