#
tokens: 2569/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
}); 
```