# Directory Structure
```
├── .gitignore
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Node.js dependencies
node_modules/
# Build output
dist/
# TypeScript cache
*.tsbuildinfo
# Environment variables and config
.env
claude_desktop_config.json
# OS files
.DS_Store
# Editor directories
.vscode/
.idea/
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Claude Web Search MCP Server
This MCP (Model Context Protocol) server provides web search capabilities using the Claude API. It allows LLMs to access up-to-date information from the web through a standardized interface.
## Features
- Web search tool using Claude's web search API
- Support for domain filtering (allowed and blocked domains)
- Configurable maximum results per search
- Automatic configuration from Claude Desktop config file
## Prerequisites
- Node.js 18 or higher
- An Anthropic API key with web search enabled
- Claude Desktop app for testing
## Installation & Setup
1. **Clone the repository:**
```bash
git clone https://github.com/Doriandarko/claude-search-mcp.git
cd claude-search-mcp
```
2. **Install dependencies:**
```bash
npm install
```
3. **Build the server:**
```bash
npm run build
```
This compiles the TypeScript code and makes the server executable.
4. **Link the server for global access:**
```bash
npm link
```
This makes the `mcp-server-claude-search` command available system-wide, allowing the Claude Desktop app to find it.
## Running the Server with Claude Desktop App
Once the server is installed and linked, the Claude Desktop app can manage it automatically if configured correctly.
1. **Configure Claude Desktop App:**
Open your Claude Desktop app's MCP server configuration file (usually `claude_desktop_config.json`). Add or update the entry for this server:
```json
{
"mcpServers": {
// ... other servers ...
"claude-search": {
"command": "mcp-server-claude-search",
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE"
}
}
// ... other servers ...
}
}
```
Replace `"YOUR_ANTHROPIC_API_KEY_HERE"` with your actual Anthropic API key. The server will also attempt to read this key from `~/code/claude-search-mcp/claude_desktop_config.json` if the `env` variable is not set here, but it's good practice to define it per-server in the main config.
2. **Launch Claude Desktop App:**
Start (or restart) your Claude Desktop application. It should now be able to find and launch the `mcp-server-claude-search` when needed.
3. **Use Web Search:**
You can now use web search capabilities in your conversations with Claude.
## Manual Server Execution (for testing/development)
If you want to run the server manually for testing or development purposes (outside of the Claude Desktop app management):
- **Using the globally linked command:**
```bash
mcp-server-claude-search
```
- **Directly with tsx (for development with auto-restart):**
```bash
npm run dev
```
- **Running the compiled code directly:**
```bash
npm start
```
## Web Search Tool Parameters
The web search tool supports the following parameters when called by an LLM:
- `query` (required): The search query string.
- `maxResults` (optional): Maximum number of search results to return (default: 5).
- `allowedDomains` (optional): Array of domains to include in search results (e.g., `["example.com", "wikipedia.org"]`).
- `blockedDomains` (optional): Array of domains to exclude from search results.
## License
MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"outDir": "./dist",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "claude-search-mcp",
"version": "1.0.0",
"description": "MCP server that provides Claude web search functionality",
"main": "dist/index.js",
"type": "module",
"bin": {
"mcp-server-claude-search": "./dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.17.0",
"@modelcontextprotocol/sdk": "^1.0.1",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.11.0",
"shx": "^0.3.4",
"tsx": "^4.7.0",
"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,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { Anthropic } from '@anthropic-ai/sdk';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
// Load environment variables as fallback
dotenv.config();
// Function to load the Claude Desktop config
function loadClaudeConfig() {
try {
const configPath = path.resolve(process.env.HOME || '', 'code', 'claude-search-mcp', 'claude_desktop_config.json');
const configData = fs.readFileSync(configPath, 'utf8');
return JSON.parse(configData);
} catch (error) {
console.warn(`Warning: Could not load Claude Desktop config: ${(error as Error).message}`);
console.warn('Falling back to environment variables');
return null;
}
}
// Get Anthropic API key from config or environment
const claudeConfig = loadClaudeConfig();
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || '';
// Initialize the Anthropic client
const anthropic = new Anthropic({
apiKey: anthropicApiKey,
});
// Define tool schema
const WEB_SEARCH_TOOL: Tool = {
name: "web_search",
description: "Search the web for real-time information about any topic. Use this tool when you need up-to-date information that might not be available in your training data, or when you need to verify current facts.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query to look up on the web"
},
maxResults: {
type: "number",
description: "Maximum number of search results to return (default: 5)"
},
allowedDomains: {
type: "array",
items: {
type: "string"
},
description: "Only include results from these domains"
},
blockedDomains: {
type: "array",
items: {
type: "string"
},
description: "Never include results from these domains"
}
},
required: ["query"]
}
};
// Create the server
const server = new Server(
{
name: "Claude Web Search",
version: "1.0.0"
},
{
capabilities: {
tools: {},
}
}
);
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [WEB_SEARCH_TOOL]
};
});
// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
try {
const { name, arguments: args } = request.params;
if (name !== "web_search") {
throw new Error(`Unknown tool: ${name}`);
}
if (!args || typeof args !== 'object') {
throw new Error("No arguments provided");
}
const { query, maxResults = 5, allowedDomains, blockedDomains } = args as any;
if (!query || typeof query !== 'string') {
throw new Error("Invalid query parameter");
}
// Prepare the web search tool configuration
const webSearchTool = {
type: "web_search_20250305",
name: "web_search",
max_uses: maxResults
};
// Add domain filtering if provided
if (allowedDomains && Array.isArray(allowedDomains) && allowedDomains.length > 0) {
(webSearchTool as any).allowed_domains = allowedDomains;
}
if (blockedDomains && Array.isArray(blockedDomains) && blockedDomains.length > 0) {
(webSearchTool as any).blocked_domains = blockedDomains;
}
// Create a Claude message with the web search
const response = await anthropic.messages.create({
model: "claude-3-7-sonnet-latest",
max_tokens: 1024,
messages: [
{
role: "user",
content: query
}
],
// @ts-ignore - Ignoring TypeScript error since Claude API does support this
tools: [webSearchTool]
});
// Extract and format the search results
let results = "";
for (const item of response.content) {
if (item.type === 'text') {
results += item.text + "\n\n";
} else if (item.type === 'web_search_tool_result' && 'content' in item && Array.isArray(item.content)) {
results += "### Search Results\n\n";
for (const result of item.content) {
if (result.type === 'web_search_result') {
results += `- [${result.title}](${result.url})\n`;
results += ` Last updated: ${result.page_age || 'Unknown'}\n\n`;
}
}
}
}
// Return the formatted results
return {
content: [{ type: "text", text: results.trim() }]
};
} catch (error) {
console.error('Error performing web search:', error);
return {
content: [{ type: "text", text: `Error performing web search: ${(error as Error).message}` }],
isError: true
};
}
});
// Run the server via stdio
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Claude Web Search MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});
```