# Directory Structure ``` ├── .gitignore ├── .python-version ├── docker-compose.yml ├── Dockerfile ├── LICENSE ├── pyproject.toml ├── README_JP.md ├── README.md ├── smithery.yaml ├── src │ ├── __init__.py │ ├── client.py │ ├── server.py │ └── taivily_client.py └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- ``` 3.13 ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Python-generated files __pycache__/ *.py[oc] build/ dist/ wheels/ *.egg-info # Virtual environments .venv ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # tavily-search MCP server A MCP server project [](https://archestra.ai/mcp-catalog/tomatio13__mcp-server-tavily) <a href="https://glama.ai/mcp/servers/s0hka6zney"><img width="380" height="200" src="https://glama.ai/mcp/servers/s0hka6zney/badge" alt="tavily-search MCP server" /></a> ## Components This server uses the Tavily API to perform searches based on specified queries. - Search results are returned in text format. - Search results include AI responses, URIs, and titles of the search results. ### Tools This server implements the following tools: - search: Performs searches based on specified queries - Required argument: "query" - Optional argument: "search_depth" (basic or advanced) ### Installing via Smithery To install Tavily Search for Claude Desktop automatically via [Smithery](https://smithery.ai/server/tavily-search): ```bash npx -y @smithery/cli install tavily-search --client claude ``` ### Install 1. Download the repository. ```bash git clone https://github.com/Tomatio13/mcp-server-tavily.git ``` 2. Open the Claude Desktop configuration file. ``` On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` On Windows: `C:\Users\[username]\AppData\Roaming\Claude\claude_desktop_config.json` ``` 3. Edit the configuration file as follows: ```yaml "mcpServers": { "tavily-search": { "command": "uv", "args": [ "--directory", "C:\\your_path\\mcp-server-tavily", "run", "tavily-search" ], "env": { "TAVILY_API_KEY": "YOUR_TAVILY_API_KEY", "PYTHONIOENCODING": "utf-8" } } } ``` 4. Restart Claude Desktop. ## Usage In Claude Desktop, when you ask "Please search for something", you will receive search results. Search example: ``` Please search in detail for today's events in Kamakura ``` Response example: ``` According to the search results, the following events start today, December 1st: "Kamakura Promotion Photo Contest 2025" Period: December 1, 2024 - January 31, 2025 A photo contest for those who love Kamakura Applications start accepting from today Also, as a related upcoming event: On December 7th, an exhibition by 12 Kamakura artists will be held at the Seibu Press Inn Kamakura Ofuna Station East Exit Lounge. ``` ## Log Storage Location Logs are stored in the following location: For Windows: ``` C:\Users\[username]\AppData\Roaming\Claude\logs\mcp-server-tavily-search ``` ## Execution with Cursor 1. Create a shell script (e.g., `script.sh`) as shown below: ```bash #!/bin/bash TARGET_DIR=/path/to/mcp-server-tavily cd "${TARGET_DIR}" export TAVILY_API_KEY="your-api-key" export PYTHONIOENCODING=utf-8 uv --directory $PWD run tavily-search ``` 2. Configure Cursor's MCP Server settings as follows: ``` Name: tavily-search Type: command Command: /path/to/your/script.sh ``` 3. Save the settings. 4. Once the settings are saved, you can ask Cursor's Composer-Agent to "search for something," and it will return the search results. ## Running in Local Environment Using Docker Compose ### Purpose For operating systems other than Windows/MacOS where Claude Desktop cannot be used, this section explains how to set up and run an MCP server and client in a local environment using Docker compose. ### Steps 1. Install Docker. 2. Download the repository. ```bash git clone https://github.com/Tomatio13/mcp-server-tavily.git ``` 3. Run Docker compose. ```bash docker compose up -d ``` 4. Execute the client. ```bash docker exec mcp_server uv --directory /usr/src/app/mcp-server-tavily/src run client.py ``` 5. Execution Results 6. After searching for available tools as shown below, a query will be issued to Tavily and a response will be returned: ```bash 2024-12-01 11:21:56,930 - tavily-search-server - INFO - Starting Tavily search server 2024-12-01 11:21:56,932 - tavily-search-server - INFO - Server initialized, starting main loop 2024-12-01 11:21:56,936 - mcp.server - INFO - Processing request of type ListToolsRequest 2024-12-01 11:21:56,936 - tavily-search-server - INFO - Listing available tools 利用可能なツール: nextCursor=None tools=[Tool(name='search', description='Search the web using Tavily API', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'Search query'}, 'search_depth': {'type': 'string', 'description': 'Search depth (basic or advanced)', 'enum': ['basic', 'advanced']}}, 'required': ['query']})] 2024-12-01 11:21:56,937 - mcp.server - INFO - Processing request of type CallToolRequest 2024-12-01 11:21:56,937 - tavily-search-server - INFO - TOOL_CALL_DEBUG: Tool called - name: search, arguments: {'query': '今日の東京タワーのイベントを教えて下さい'} 2024-12-01 11:21:56,937 - tavily-search-server - INFO - Executing search with query: '今日の東京タワーのイベントを教えて下さい' 2024-12-01 11:22:00,243 - httpx - INFO - HTTP Request: POST https://api.tavily.com/search "HTTP/1.1 200 OK" 2024-12-01 11:22:00,243 - tavily-search-server - INFO - Search successful - Answer generated 2024-12-01 11:22:00,243 - tavily-search-server - INFO - Search successful - Results available ツール実行結果: content=[TextContent(type='text', text='AI Answer:\n今日の東京タワーのイベントは以下の通りです:\n1. Candlelight: エド・シーランとコールドプレイのヒットメドレー - 12月01日\n2. チームラボプラネッツ TOKYO - 12月01日から1月21日\n\n他にもイベントがある可能性がありますので、公式ウェブサイト等で最新情報をご確認ください。\n\n\n\nSearch Results:\n\n1. 東京タワー (東京): 現在のイベントとチケット | Fever\nURL: https://feverup.com/ja/tokyo/venue/tokyo-tower\nSummary: Summary not found\n\n\n2. 東京タワー(東京都)の施設で開催するイベント一覧|ウォーカープラス\nURL: https://www.walkerplus.com/spot/ar0313s03867/e_list.html\nSummary: Summary not found\n\n\n3. 東京タワー - Tokyo Tower\nURL: https://www.tokyotower.co.jp/event/\nSummary: Summary not found\n')] isError=False ``` ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml services: app: image: ghcr.io/astral-sh/uv:python3.12-bookworm container_name: mcp_server volumes: - .:/usr/src/app/mcp-server-tavily working_dir: /usr/src/app command: tail -f /dev/null environment: - PYTHONUNBUFFERED=1 - TAVILY_API_KEY=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 extra_hosts: - "host.docker.internal:host-gateway" ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [project] name = "tavily-search" version = "0.1.0" description = "A MCP server project" readme = "README.md" requires-python = ">=3.13" dependencies = [ "httpx>=0.28.0", "mcp>=1.0.0", "python-dotenv>=1.0.1", "tavily-python>=0.5.0", ] [build-system] requires = [ "hatchling",] build-backend = "hatchling.build" [project.scripts] tavily-search = "src:main" [tool.hatch.build.targets.wheel] packages = ["src"] ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - tavilyApiKey properties: tavilyApiKey: type: string description: The API key for the Tavily Search server. commandFunction: # A function that produces the CLI command to start the MCP on stdio. |- (config) => ({ command: 'uv', args: ['--directory', '/app/src', 'run', 'server.py'], env: { TAVILY_API_KEY: config.tavilyApiKey } }) ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile # Use a Python image with uv pre-installed FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv # Install the project into /app WORKDIR /app # Enable bytecode compilation ENV UV_COMPILE_BYTECODE=1 # Copy from the cache instead of linking since it's a mounted volume ENV UV_LINK_MODE=copy # Install the project's dependencies using the lockfile and settings COPY pyproject.toml uv.lock ./ COPY README.md uv.lock ./ RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-install-project --no-dev --no-editable # Then, add the rest of the project source code and install it # Installing separately from its dependencies allows optimal layer caching COPY src/ /app/src/ RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-editable FROM python:3.12-slim-bookworm WORKDIR /app COPY --from=uv /root/.local /root/.local COPY --from=uv --chown=app:app /app/.venv /app/.venv # Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" # Set necessary environment variables ENV TAVILY_API_KEY=your_api_key_here ENTRYPOINT ["uv", "--directory", "/app/src", "run", "server.py"] ``` -------------------------------------------------------------------------------- /src/server.py: -------------------------------------------------------------------------------- ```python import os import json import logging from datetime import datetime from collections.abc import Sequence from typing import Any, Optional from tavily import AsyncTavilyClient from dotenv import load_dotenv from mcp.server import Server from mcp.types import ( Resource, Tool, TextContent, ImageContent, EmbeddedResource, EmptyResult ) from pydantic import AnyUrl import asyncio # 環境変数の読み込み load_dotenv() # ログの準備 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("tavily-search-server") # APIキーの準備 API_KEY = os.getenv("TAVILY_API_KEY") if not API_KEY: logger.error("TAVILY_API_KEY environment variable not found") raise ValueError("TAVILY_API_KEY environment variable required") # サーバの準備 app = Server("tavily-search-server") @app.list_resources() async def list_resources() -> list[Resource]: logger.info("Listing available resources") resources = [ Resource( uri=AnyUrl(f"websearch://query=`who is current Prime Minister of Japan 2024`,search_depth=`basic`"), name="Web Search about `who is current Prime Minister of Japan 2024`.\ There are two types of search_depth: 'basic' and 'advanced', with 'advanced' searching deeper.'", mimeType="application/json", description="General web search using Tavily API" ) ] logger.debug(f"Returning resources with full content: {resources}") return resources # 利用可能なツール一覧の取得 @app.list_tools() async def list_tools() -> list[Tool]: """利用可能なツールの一覧を返す""" logger.info("Listing available tools") tools = [ Tool( name="search", description="Search the web using Tavily API", inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "search_depth": { "type": "string", "description": "Search depth (basic or advanced)", "enum": ["basic", "advanced"] } }, "required": ["query"] } ) ] logger.debug(f"Returning tools: {tools}") return tools # 検索結果を処理する関数 async def process_search_results(results: dict) -> TextContent: """検索結果を処理してTextContentを返す""" if not results: logger.warning("Empty search results received") return TextContent( type="text", text="No results were found for your query. Please try a different search term." ) # 結果を格納する文字列を作成 response_text = [] # AI回答を追加 if 'answer' in results and results['answer']: logger.info("Search successful - Answer generated") response_text.append("AI Answer:") response_text.append(results['answer']) response_text.append("\n") # 検索結果を追加 if 'results' in results and results['results']: logger.info("Search successful - Results available") response_text.append("\nSearch Results:") for i, result in enumerate(results['results'], 1): response_text.append(f"\n{i}. {result.get('title', 'Title not found')}") response_text.append(f"URL: {result.get('url', 'URL not found')}") response_text.append(f"Summary: {result.get('snippet', 'Summary not found')}\n") if response_text: return TextContent(type="text", text="\n".join(response_text)) logger.warning("No answer or results found in search results") return TextContent( type="text", text="The search was completed but no relevant information was found. Please try refining your query." ) # ツールの呼び出し @app.call_tool() async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]: """ツールを呼び出して結果を返す""" logger.info(f"TOOL_CALL_DEBUG: Tool called - name: {name}, arguments: {arguments}") if name != "search": logger.error(f"Unknown tool requested: {name}") return [TextContent( type="text", text=f"Error: Unknown tool '{name}'. Only 'search' is supported." )] if not isinstance(arguments, dict) or "query" not in arguments: logger.error(f"Invalid arguments provided: {arguments}") return [TextContent( type="text", text="Error: Invalid arguments. A 'query' parameter is required." )] try: client = AsyncTavilyClient(API_KEY) query = arguments["query"] logger.info(f"Executing search with query: '{query}'") search_task = client.search( query=query, search_depth=arguments.get("search_depth", "basic"), include_images=False, include_answer=True, max_results=3, topic="general" ) try: # タイムアウトを設定して検索を実行 results = await asyncio.wait_for(search_task, timeout=30.0) logger.debug(f"Raw search results: {results}") # 結果の処理 return [await process_search_results(results)] except asyncio.TimeoutError: logger.error("Search operation timed out after 30 seconds") return [TextContent( type="text", text="The search operation timed out. Please try again with a more specific query or check your internet connection." )] except Exception as e: error_message = str(e) logger.error(f"Search failed: {error_message}", exc_info=True) # エラーメッセージをユーザーフレンドリー形式に変換 if "api_key" in error_message.lower(): return [TextContent( type="text", text="Authentication error occurred. Please check the API key configuration." )] elif "rate limit" in error_message.lower(): return [TextContent( type="text", text="Rate limit exceeded. Please wait a moment before trying again." )] else: return [TextContent( type="text", text=f"An unexpected error occurred during the search. Please try again later. Error: {error_message}" )] # メイン実行部分 async def main(): logger.info("Starting Tavily search server") try: from mcp.server.stdio import stdio_server async with stdio_server() as (read_stream, write_stream): logger.info("Server initialized, starting main loop") await app.run( read_stream, write_stream, app.create_initialization_options() ) except Exception as e: logger.error(f"Server failed to start: {str(e)}", exc_info=True) raise # エントリーポイント追加 if __name__ == "__main__": main_entry() def main_entry(): try: asyncio.run(main()) except KeyboardInterrupt: logger.info("Server shutdown requested") except Exception as e: logger.error(f"Server error: {e}", exc_info=True) raise ```