From 0784802e81d0365742a2db845c5960eedbf72102 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:26:43 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 14 +++++ README.md | 3 + hooks/hooks.json | 34 ++++++++++++ hooks/scripts/tavily_extract_to_advanced.py | 46 ++++++++++++++++ hooks/scripts/webfetch_to_tavily_extract.py | 23 ++++++++ hooks/scripts/websearch_to_tavily_search.py | 24 ++++++++ plugin.lock.json | 61 +++++++++++++++++++++ skills/tavily-usage/SKILL.md | 58 ++++++++++++++++++++ 8 files changed, 263 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 hooks/hooks.json create mode 100755 hooks/scripts/tavily_extract_to_advanced.py create mode 100755 hooks/scripts/webfetch_to_tavily_extract.py create mode 100755 hooks/scripts/websearch_to_tavily_search.py create mode 100644 plugin.lock.json create mode 100644 skills/tavily-usage/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..d6df788 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "websearch-tools", + "description": "Smart web tool routing via hooks that redirect WebFetch/WebSearch to Tavily. Includes context7 MCP for library docs and a skill for tool selection.", + "version": "1.2.1", + "author": { + "name": "Fatih Akyon" + }, + "skills": [ + "./skills" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc91a62 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# websearch-tools + +Smart web tool routing via hooks that redirect WebFetch/WebSearch to Tavily. Includes context7 MCP for library docs and a skill for tool selection. diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..5555096 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,34 @@ +{ + "description": "Web search and Tavily integration hooks", + "hooks": { + "PreToolUse": [ + { + "matcher": "WebFetch", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/webfetch_to_tavily_extract.py" + } + ] + }, + { + "matcher": "WebSearch", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/websearch_to_tavily_search.py" + } + ] + }, + { + "matcher": "mcp__tavily__tavily-extract", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/tavily_extract_to_advanced.py" + } + ] + } + ] + } +} diff --git a/hooks/scripts/tavily_extract_to_advanced.py b/hooks/scripts/tavily_extract_to_advanced.py new file mode 100755 index 0000000..a7b085c --- /dev/null +++ b/hooks/scripts/tavily_extract_to_advanced.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +PreToolUse hook: intercept mcp__tavily__tavily-extract +- Block GitHub URLs and suggest using GitHub MCP tools instead +- Otherwise, upgrade extract_depth to "advanced" +""" +import json +import sys + +try: + data = json.load(sys.stdin) + tool_input = data["tool_input"] + urls = tool_input.get("urls", []) + + # Check for GitHub URLs + github_domains = ("github.com", "raw.githubusercontent.com", "gist.github.com") + github_urls = [url for url in urls if any(domain in url for domain in github_domains)] + + if github_urls: + # Block and suggest GitHub MCP tools + print(json.dumps({ + "systemMessage": "GitHub URL detected in Tavily extract tool. AI is directed to use GitHub MCP tools instead.", + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": "GitHub URL detected. Please use GitHub MCP tools (mcp__github__*) for more robust data retrieval." + }, + }, separators=(',', ':'))) + sys.exit(2) + + # Always ensure extract_depth="advanced" for non-GitHub URLs + tool_input["extract_depth"] = "advanced" + + # Allow the call to proceed + print(json.dumps({ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow", + "permissionDecisionReason": "Automatically upgrading Tavily extract to advanced mode for better content extraction" + } + }, separators=(',', ':'))) + sys.exit(0) + +except (KeyError, json.JSONDecodeError) as err: + print(f"hook-error: {err}", file=sys.stderr) + sys.exit(1) \ No newline at end of file diff --git a/hooks/scripts/webfetch_to_tavily_extract.py b/hooks/scripts/webfetch_to_tavily_extract.py new file mode 100755 index 0000000..559ad3d --- /dev/null +++ b/hooks/scripts/webfetch_to_tavily_extract.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +PreToolUse hook: intercept WebFetch → suggest using tavily-extract instead +""" +import json +import sys + +try: + data = json.load(sys.stdin) + url = data["tool_input"]["url"] +except (KeyError, json.JSONDecodeError) as err: + print(f"hook-error: {err}", file=sys.stderr) + sys.exit(1) + +print(json.dumps({ + "systemMessage": "WebFetch detected. AI is directed to use Tavily extract instead.", + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": f"Please use mcp__tavily__tavily-extract with urls: ['{url}'] and extract_depth: 'advanced'" + } +}, separators=(',', ':'))) +sys.exit(0) diff --git a/hooks/scripts/websearch_to_tavily_search.py b/hooks/scripts/websearch_to_tavily_search.py new file mode 100755 index 0000000..3d9dd48 --- /dev/null +++ b/hooks/scripts/websearch_to_tavily_search.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +""" +PreToolUse hook: intercept WebSearch → suggest using Tavily search instead +""" +import json +import sys + +try: + data = json.load(sys.stdin) + tool_input = data["tool_input"] + query = tool_input["query"] +except (KeyError, json.JSONDecodeError) as err: + print(f"hook-error: {err}", file=sys.stderr) + sys.exit(1) + +print(json.dumps({ + "systemMessage": "WebSearch detected. AI is directed to use Tavily search instead.", + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": f"Please use mcp__tavily__tavily-search with query: '{query}'" + } +}, separators=(',', ':'))) +sys.exit(0) \ No newline at end of file diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..1aad233 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,61 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:fcakyon/claude-codex-settings:plugins/websearch-tools", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "3daa2b5dd45909cccf5270b0c3fa5c6ac0aa8aa6", + "treeHash": "e01bd8701c7767c6854e87b53500da994ab2610aa49bbf049e6e7c28cd0b729b", + "generatedAt": "2025-11-28T10:16:50.949294Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "websearch-tools", + "description": "Smart web tool routing via hooks that redirect WebFetch/WebSearch to Tavily. Includes context7 MCP for library docs and a skill for tool selection.", + "version": "1.2.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "1fe0726bb9aa50266c7ee33eff5abdbe00e1e8727f2f9737331968ab62f19a2e" + }, + { + "path": "hooks/hooks.json", + "sha256": "b922d7c24ce3f5fdb6581ba8bb7da2523cde43f4fb851dc112e46d031dd075cd" + }, + { + "path": "hooks/scripts/webfetch_to_tavily_extract.py", + "sha256": "ac3433d5a252e9c651138bac783bc6dc551c96e6e2dedefe336d702c9b11c6dd" + }, + { + "path": "hooks/scripts/websearch_to_tavily_search.py", + "sha256": "25543d9601135aaf3e1a99fb184cc12b02c0faeee6443af80d1eab203dba88a6" + }, + { + "path": "hooks/scripts/tavily_extract_to_advanced.py", + "sha256": "b0fa4377fa04fc3ad60f5b6b4b10bf39d1d7cfe6d03b53b3e0a4a36fa45e5947" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "46220c8dc463e12961cd427674d596154335619d21659396de356bfee40c7ffa" + }, + { + "path": "skills/tavily-usage/SKILL.md", + "sha256": "4ea803b1ba8db68521e8a28484962497a01bced49f313f6dbb1e00725586830a" + } + ], + "dirSha256": "e01bd8701c7767c6854e87b53500da994ab2610aa49bbf049e6e7c28cd0b729b" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/tavily-usage/SKILL.md b/skills/tavily-usage/SKILL.md new file mode 100644 index 0000000..754471c --- /dev/null +++ b/skills/tavily-usage/SKILL.md @@ -0,0 +1,58 @@ +--- +name: tavily-usage +description: This skill should be used when user asks to "search the web", "fetch content from URL", "extract page content", "use Tavily search", "scrape this website", "get information from this link", or "web search for X". +--- + +# Tavily Search and Extract + +Use Tavily MCP tools for web search and content retrieval operations. + +## Tool Selection + +### Tavily Search (`mcp__tavily__tavily-search`) + +Use for: + +- Keyword-based searches across the web +- Finding relevant pages and content +- Quick answer gathering +- Multiple result discovery + +**Best for**: Initial research, finding sources, broad queries + +### Tavily Extract (`mcp__tavily__tavily-extract`) + +Use for: + +- Getting detailed content from specific URLs +- Deep analysis of page content +- Structured data extraction +- Following up on search results + +**Best for**: In-depth analysis, specific URL content, detailed information + +## Hook Behavior + +`tavily_extract_to_advanced.py` hook automatically upgrades extract calls to advanced mode for better accuracy when needed. + +## Integration Pattern + +1. Use `mcp__tavily__tavily-search` for discovery phase +2. Analyze results to find relevant URLs +3. Use `mcp__tavily__tavily-extract` for detailed content on specific URLs +4. Process extracted content for user needs + +## Environment Variables + +Tavily MCP requires: + +- `TAVILY_API_KEY` - API key from Tavily (tvly-...) + +Configure in shell before using the plugin. + +## Cost Considerations + +- Search is cheaper than extract +- Use search to filter relevant URLs first +- Only extract URLs that are likely relevant +- Cache results when possible