#
tokens: 5821/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | 3.13
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # tavily-search MCP server
  2 | 
  3 | A MCP server project
  4 | 
  5 | [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Tomatio13/mcp-server-tavily)](https://archestra.ai/mcp-catalog/tomatio13__mcp-server-tavily)
  6 | <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>
  7 | 
  8 | ## Components
  9 | 
 10 | This server uses the Tavily API to perform searches based on specified queries.
 11 | - Search results are returned in text format.
 12 | - Search results include AI responses, URIs, and titles of the search results.
 13 | 
 14 | ### Tools
 15 | 
 16 | This server implements the following tools:
 17 | - search: Performs searches based on specified queries
 18 |   - Required argument: "query"
 19 |   - Optional argument: "search_depth" (basic or advanced)
 20 | 
 21 | ### Installing via Smithery
 22 | 
 23 | To install Tavily Search for Claude Desktop automatically via [Smithery](https://smithery.ai/server/tavily-search):
 24 | 
 25 | ```bash
 26 | npx -y @smithery/cli install tavily-search --client claude
 27 | ```
 28 | 
 29 | ### Install
 30 | 
 31 | 1. Download the repository.
 32 | ```bash
 33 | git clone https://github.com/Tomatio13/mcp-server-tavily.git
 34 | ``` 
 35 | 2. Open the Claude Desktop configuration file.
 36 | ```
 37 | On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
 38 | On Windows: `C:\Users\[username]\AppData\Roaming\Claude\claude_desktop_config.json`
 39 | ```
 40 | 
 41 | 3. Edit the configuration file as follows:
 42 |   ```yaml
 43 |   "mcpServers": {
 44 |     "tavily-search": {
 45 |       "command": "uv",
 46 |       "args": [
 47 |         "--directory",
 48 |         "C:\\your_path\\mcp-server-tavily",
 49 |         "run",
 50 |         "tavily-search"
 51 |       ],
 52 |       "env": {
 53 |         "TAVILY_API_KEY": "YOUR_TAVILY_API_KEY",
 54 |         "PYTHONIOENCODING": "utf-8"
 55 |       }
 56 |     }
 57 |   }
 58 |   ```
 59 | 
 60 | 4. Restart Claude Desktop.
 61 | 
 62 | ## Usage
 63 | 
 64 | In Claude Desktop, when you ask "Please search for something", you will receive search results.
 65 | 
 66 | Search example:
 67 | ```
 68 | Please search in detail for today's events in Kamakura
 69 | ```
 70 | Response example:
 71 | ```
 72 | According to the search results, the following events start today, December 1st:
 73 | "Kamakura Promotion Photo Contest 2025"
 74 | Period: December 1, 2024 - January 31, 2025
 75 | A photo contest for those who love Kamakura
 76 | Applications start accepting from today
 77 | Also, as a related upcoming event:
 78 | On December 7th, an exhibition by 12 Kamakura artists will be held at the Seibu Press Inn Kamakura Ofuna Station East Exit Lounge.
 79 | ```
 80 | 
 81 | ## Log Storage Location
 82 | 
 83 | Logs are stored in the following location:
 84 | 
 85 | For Windows:
 86 | ```
 87 | C:\Users\[username]\AppData\Roaming\Claude\logs\mcp-server-tavily-search
 88 | ```
 89 | ## Execution with Cursor
 90 | 
 91 | 1. Create a shell script (e.g., `script.sh`) as shown below:
 92 | 
 93 | ```bash
 94 | #!/bin/bash
 95 | TARGET_DIR=/path/to/mcp-server-tavily
 96 | cd "${TARGET_DIR}"
 97 | export TAVILY_API_KEY="your-api-key"
 98 | export PYTHONIOENCODING=utf-8
 99 | uv --directory $PWD run tavily-search
100 | ```
101 | 
102 | 2. Configure Cursor's MCP Server settings as follows:
103 | 
104 | ```
105 | Name: tavily-search
106 | Type: command
107 | Command: /path/to/your/script.sh
108 | ```
109 | 
110 | 3. Save the settings.
111 | 
112 | 4. Once the settings are saved, you can ask Cursor's Composer-Agent to "search for something," and it will return the search results.
113 | 
114 | ## Running in Local Environment Using Docker Compose
115 | 
116 | ### Purpose
117 | For operating systems other than Windows/MacOS where Claude Desktop cannot be used,
118 | this section explains how to set up and run an MCP server and client in a local environment
119 | using Docker compose.
120 | 
121 | ### Steps
122 | 1. Install Docker.
123 | 2. Download the repository.
124 | ```bash
125 | git clone https://github.com/Tomatio13/mcp-server-tavily.git
126 | ``` 
127 | 3. Run Docker compose.
128 | ```bash
129 | docker compose up -d
130 | ``` 
131 | 4. Execute the client.
132 | ```bash
133 | docker exec mcp_server uv --directory /usr/src/app/mcp-server-tavily/src run client.py
134 | ```
135 | 5. Execution Results
136 | 6. After searching for available tools as shown below, a query will be issued to Tavily and a response will be returned:
137 | ```bash
138 | 2024-12-01 11:21:56,930 - tavily-search-server - INFO - Starting Tavily search server
139 | 2024-12-01 11:21:56,932 - tavily-search-server - INFO - Server initialized, starting main loop
140 | 2024-12-01 11:21:56,936 - mcp.server - INFO - Processing request of type ListToolsRequest
141 | 2024-12-01 11:21:56,936 - tavily-search-server - INFO - Listing available tools
142 | 利用可能なツール: 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']})]
143 | 2024-12-01 11:21:56,937 - mcp.server - INFO - Processing request of type CallToolRequest
144 | 2024-12-01 11:21:56,937 - tavily-search-server - INFO - TOOL_CALL_DEBUG: Tool called - name: search, arguments: {'query': '今日の東京タワーのイベントを教えて下さい'}
145 | 2024-12-01 11:21:56,937 - tavily-search-server - INFO - Executing search with query: '今日の東京タワーのイベントを教えて下さい'
146 | 2024-12-01 11:22:00,243 - httpx - INFO - HTTP Request: POST https://api.tavily.com/search "HTTP/1.1 200 OK"
147 | 2024-12-01 11:22:00,243 - tavily-search-server - INFO - Search successful - Answer generated
148 | 2024-12-01 11:22:00,243 - tavily-search-server - INFO - Search successful - Results available
149 | ツール実行結果: 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
150 | ``` 
151 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | services:
 2 |   app:
 3 |     image: ghcr.io/astral-sh/uv:python3.12-bookworm
 4 |     container_name: mcp_server
 5 |     volumes:
 6 |       - .:/usr/src/app/mcp-server-tavily
 7 |     working_dir: /usr/src/app
 8 |     command: tail -f /dev/null
 9 |     environment:
10 |       - PYTHONUNBUFFERED=1
11 |       - TAVILY_API_KEY=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
12 |     extra_hosts:
13 |       - "host.docker.internal:host-gateway"
14 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "tavily-search"
 3 | version = "0.1.0"
 4 | description = "A MCP server project"
 5 | readme = "README.md"
 6 | requires-python = ">=3.13"
 7 | dependencies = [
 8 |  "httpx>=0.28.0",
 9 |  "mcp>=1.0.0",
10 |  "python-dotenv>=1.0.1",
11 |  "tavily-python>=0.5.0",
12 | ]
13 | 
14 | [build-system]
15 | requires = [ "hatchling",]
16 | build-backend = "hatchling.build"
17 | 
18 | [project.scripts]
19 | tavily-search = "src:main"
20 | 
21 | [tool.hatch.build.targets.wheel]
22 | packages = ["src"]
23 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - tavilyApiKey
10 |     properties:
11 |       tavilyApiKey:
12 |         type: string
13 |         description: The API key for the Tavily Search server.
14 |   commandFunction:
15 |     # A function that produces the CLI command to start the MCP on stdio.
16 |     |-
17 |     (config) => ({ command: 'uv', args: ['--directory', '/app/src', 'run', 'server.py'], env: { TAVILY_API_KEY: config.tavilyApiKey } })
18 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Use a Python image with uv pre-installed
 3 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv
 4 | 
 5 | # Install the project into /app
 6 | WORKDIR /app
 7 | 
 8 | # Enable bytecode compilation
 9 | ENV UV_COMPILE_BYTECODE=1
10 | 
11 | # Copy from the cache instead of linking since it's a mounted volume
12 | ENV UV_LINK_MODE=copy
13 | 
14 | # Install the project's dependencies using the lockfile and settings
15 | COPY pyproject.toml uv.lock ./
16 | COPY README.md uv.lock ./
17 | RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-install-project --no-dev --no-editable
18 | 
19 | # Then, add the rest of the project source code and install it
20 | # Installing separately from its dependencies allows optimal layer caching
21 | COPY src/ /app/src/
22 | RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-editable
23 | 
24 | FROM python:3.12-slim-bookworm
25 | 
26 | WORKDIR /app
27 | 
28 | COPY --from=uv /root/.local /root/.local
29 | COPY --from=uv --chown=app:app /app/.venv /app/.venv
30 | 
31 | # Place executables in the environment at the front of the path
32 | ENV PATH="/app/.venv/bin:$PATH"
33 | 
34 | # Set necessary environment variables
35 | ENV TAVILY_API_KEY=your_api_key_here
36 | 
37 | ENTRYPOINT ["uv", "--directory", "/app/src", "run", "server.py"]
38 | 
```

--------------------------------------------------------------------------------
/src/server.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | import json
  3 | import logging
  4 | from datetime import datetime
  5 | from collections.abc import Sequence
  6 | from typing import Any, Optional
  7 | from tavily import AsyncTavilyClient
  8 | from dotenv import load_dotenv
  9 | from mcp.server import Server
 10 | from mcp.types import (
 11 |     Resource,
 12 |     Tool,
 13 |     TextContent,
 14 |     ImageContent,
 15 |     EmbeddedResource,
 16 |     EmptyResult
 17 | )
 18 | from pydantic import AnyUrl
 19 | import asyncio
 20 | 
 21 | # 環境変数の読み込み
 22 | load_dotenv()
 23 | 
 24 | # ログの準備
 25 | logging.basicConfig(
 26 |     level=logging.INFO,
 27 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 28 | )
 29 | logger = logging.getLogger("tavily-search-server")
 30 | 
 31 | # APIキーの準備
 32 | API_KEY = os.getenv("TAVILY_API_KEY")
 33 | if not API_KEY:
 34 |     logger.error("TAVILY_API_KEY environment variable not found")
 35 |     raise ValueError("TAVILY_API_KEY environment variable required")
 36 | 
 37 | # サーバの準備
 38 | app = Server("tavily-search-server")
 39 | 
 40 | @app.list_resources()
 41 | async def list_resources() -> list[Resource]:
 42 |     logger.info("Listing available resources")
 43 |     resources = [
 44 |         Resource(
 45 |             uri=AnyUrl(f"websearch://query=`who is current Prime Minister of Japan 2024`,search_depth=`basic`"),
 46 |             name="Web Search about `who is current Prime Minister of Japan 2024`.\
 47 |                 There are two types of search_depth: 'basic' and 'advanced', with 'advanced' searching deeper.'",
 48 |             mimeType="application/json",
 49 |             description="General web search using Tavily API"
 50 |         )
 51 |     ]
 52 |     logger.debug(f"Returning resources with full content: {resources}")
 53 |     return resources
 54 |     
 55 | 
 56 | # 利用可能なツール一覧の取得
 57 | @app.list_tools()
 58 | async def list_tools() -> list[Tool]:
 59 |     """利用可能なツールの一覧を返す"""
 60 |     logger.info("Listing available tools")
 61 |     tools = [
 62 |         Tool(
 63 |             name="search",
 64 |             description="Search the web using Tavily API",
 65 |             inputSchema={
 66 |                 "type": "object",
 67 |                 "properties": {
 68 |                     "query": {
 69 |                         "type": "string",
 70 |                         "description": "Search query"
 71 |                     },
 72 |                     "search_depth": {
 73 |                         "type": "string",
 74 |                         "description": "Search depth (basic or advanced)",
 75 |                         "enum": ["basic", "advanced"]
 76 |                     }
 77 |                 },
 78 |                 "required": ["query"]
 79 |             }
 80 |         )
 81 |     ]
 82 |     logger.debug(f"Returning tools: {tools}")
 83 |     return tools
 84 | 
 85 | # 検索結果を処理する関数
 86 | async def process_search_results(results: dict) -> TextContent:
 87 |     """検索結果を処理してTextContentを返す"""
 88 |     if not results:
 89 |         logger.warning("Empty search results received")
 90 |         return TextContent(
 91 |             type="text",
 92 |             text="No results were found for your query. Please try a different search term."
 93 |         )
 94 | 
 95 |     # 結果を格納する文字列を作成
 96 |     response_text = []
 97 | 
 98 |     # AI回答を追加
 99 |     if 'answer' in results and results['answer']:
100 |         logger.info("Search successful - Answer generated")
101 |         response_text.append("AI Answer:")
102 |         response_text.append(results['answer'])
103 |         response_text.append("\n")
104 | 
105 |     # 検索結果を追加
106 |     if 'results' in results and results['results']:
107 |         logger.info("Search successful - Results available")
108 |         response_text.append("\nSearch Results:")
109 |         for i, result in enumerate(results['results'], 1):
110 |             response_text.append(f"\n{i}. {result.get('title', 'Title not found')}")
111 |             response_text.append(f"URL: {result.get('url', 'URL not found')}")
112 |             response_text.append(f"Summary: {result.get('snippet', 'Summary not found')}\n")
113 | 
114 |     if response_text:
115 |         return TextContent(type="text", text="\n".join(response_text))
116 |     
117 |     logger.warning("No answer or results found in search results")
118 |     return TextContent(
119 |         type="text",
120 |         text="The search was completed but no relevant information was found. Please try refining your query."
121 |     )
122 | 
123 | # ツールの呼び出し
124 | @app.call_tool()
125 | async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
126 |     """ツールを呼び出して結果を返す"""
127 |     logger.info(f"TOOL_CALL_DEBUG: Tool called - name: {name}, arguments: {arguments}")
128 |     
129 |     if name != "search":
130 |         logger.error(f"Unknown tool requested: {name}")
131 |         return [TextContent(
132 |             type="text",
133 |             text=f"Error: Unknown tool '{name}'. Only 'search' is supported."
134 |         )]
135 | 
136 |     if not isinstance(arguments, dict) or "query" not in arguments:
137 |         logger.error(f"Invalid arguments provided: {arguments}")
138 |         return [TextContent(
139 |             type="text",
140 |             text="Error: Invalid arguments. A 'query' parameter is required."
141 |         )]
142 | 
143 |     try:
144 |         client = AsyncTavilyClient(API_KEY)
145 |         query = arguments["query"]
146 |         
147 |         logger.info(f"Executing search with query: '{query}'")
148 | 
149 |         search_task = client.search(
150 |             query=query,
151 |             search_depth=arguments.get("search_depth", "basic"),
152 |             include_images=False,
153 |             include_answer=True,
154 |             max_results=3,
155 |             topic="general"
156 |         )
157 | 
158 |         try:
159 |             # タイムアウトを設定して検索を実行
160 |             results = await asyncio.wait_for(search_task, timeout=30.0)
161 |             logger.debug(f"Raw search results: {results}")
162 |             
163 |             # 結果の処理
164 |             return [await process_search_results(results)]
165 | 
166 |         except asyncio.TimeoutError:
167 |             logger.error("Search operation timed out after 30 seconds")
168 |             return [TextContent(
169 |                 type="text",
170 |                 text="The search operation timed out. Please try again with a more specific query or check your internet connection."
171 |             )]
172 |             
173 |     except Exception as e:
174 |         error_message = str(e)
175 |         logger.error(f"Search failed: {error_message}", exc_info=True)
176 |         
177 |         # エラーメッセージをユーザーフレンドリー形式に変換
178 |         if "api_key" in error_message.lower():
179 |             return [TextContent(
180 |                 type="text",
181 |                 text="Authentication error occurred. Please check the API key configuration."
182 |             )]
183 |         elif "rate limit" in error_message.lower():
184 |             return [TextContent(
185 |                 type="text",
186 |                 text="Rate limit exceeded. Please wait a moment before trying again."
187 |             )]
188 |         else:
189 |             return [TextContent(
190 |                 type="text",
191 |                 text=f"An unexpected error occurred during the search. Please try again later. Error: {error_message}"
192 |             )]
193 | 
194 | # メイン実行部分
195 | async def main():
196 |     logger.info("Starting Tavily search server")
197 |     try:
198 |         from mcp.server.stdio import stdio_server
199 | 
200 |         async with stdio_server() as (read_stream, write_stream):
201 |             logger.info("Server initialized, starting main loop")
202 |             await app.run(
203 |                 read_stream,
204 |                 write_stream,
205 |                 app.create_initialization_options()
206 |             )
207 | 
208 |     except Exception as e:
209 |         logger.error(f"Server failed to start: {str(e)}", exc_info=True)
210 |         raise
211 | 
212 | # エントリーポイント追加
213 | if __name__ == "__main__":
214 |     main_entry()
215 | 
216 | def main_entry():
217 |     try:
218 |         asyncio.run(main())
219 |     except KeyboardInterrupt:
220 |         logger.info("Server shutdown requested")
221 |     except Exception as e:
222 |         logger.error(f"Server error: {e}", exc_info=True)
223 |         raise
224 | 
```