liusijin revised this gist 1 month ago. Go to revision
1 file changed, 8 insertions, 2 deletions
Context7.py
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | import json | |
| 5 | 5 | import urllib.request | |
| 6 | 6 | import urllib.parse | |
| 7 | + | import ssl | |
| 7 | 8 | import os | |
| 8 | 9 | ||
| 9 | 10 | CONTEXT7_API = "https://context7.com/api" | |
| @@ -25,8 +26,13 @@ def context7_get(endpoint, params=None): | |||
| 25 | 26 | if API_KEY: | |
| 26 | 27 | headers["Authorization"] = f"Bearer {API_KEY}" | |
| 27 | 28 | ||
| 29 | + | # Create SSL context for mitmproxy compatibility | |
| 30 | + | ssl_context = ssl.create_default_context() | |
| 31 | + | ssl_context.check_hostname = False | |
| 32 | + | ssl_context.verify_mode = ssl.CERT_NONE | |
| 33 | + | ||
| 28 | 34 | req = urllib.request.Request(url, headers=headers) | |
| 29 | - | resp = urllib.request.urlopen(req, timeout=30) | |
| 35 | + | resp = urllib.request.urlopen(req, timeout=30, context=ssl_context) | |
| 30 | 36 | ||
| 31 | 37 | content_type = resp.headers.get("Content-Type", "") | |
| 32 | 38 | if "application/json" in content_type: | |
| @@ -34,7 +40,7 @@ def context7_get(endpoint, params=None): | |||
| 34 | 40 | else: | |
| 35 | 41 | return resp.read().decode() | |
| 36 | 42 | except Exception as e: | |
| 37 | - | return {"error": str(e)} | |
| 43 | + | return {"error": f"{type(e).__name__}: {e}"} | |
| 38 | 44 | ||
| 39 | 45 | @register_tool( | |
| 40 | 46 | "context7_search", | |
liusijin revised this gist 1 month ago. Go to revision
No changes
liusijin revised this gist 1 month ago. Go to revision
No changes
liusijin revised this gist 1 month ago. Go to revision
1 file changed, 0 insertions, 0 deletions
context7.py renamed to Context7.py
File renamed without changes
liusijin revised this gist 1 month ago. Go to revision
1 file changed, 0 insertions, 0 deletions
context7 renamed to context7.py
File renamed without changes
liusijin revised this gist 1 month ago. Go to revision
No changes
liusijin revised this gist 1 month ago. Go to revision
1 file changed, 117 insertions
context7(file created)
| @@ -0,0 +1,117 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 2 | + | """Context7 extension - Query up-to-date documentation for any programming library""" | |
| 3 | + | ||
| 4 | + | import json | |
| 5 | + | import urllib.request | |
| 6 | + | import urllib.parse | |
| 7 | + | import os | |
| 8 | + | ||
| 9 | + | CONTEXT7_API = "https://context7.com/api" | |
| 10 | + | API_KEY = os.environ.get("CONTEXT7_API_KEY") # Optional, for higher rate limits | |
| 11 | + | ||
| 12 | + | def context7_get(endpoint, params=None): | |
| 13 | + | """Make GET request to Context7 API""" | |
| 14 | + | try: | |
| 15 | + | url = f"{CONTEXT7_API}{endpoint}" | |
| 16 | + | if params: | |
| 17 | + | url += "?" + urllib.parse.urlencode(params) | |
| 18 | + | ||
| 19 | + | headers = { | |
| 20 | + | "X-Context7-Source": "mcp", | |
| 21 | + | "Accept": "application/json" | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | # Add API key if available | |
| 25 | + | if API_KEY: | |
| 26 | + | headers["Authorization"] = f"Bearer {API_KEY}" | |
| 27 | + | ||
| 28 | + | req = urllib.request.Request(url, headers=headers) | |
| 29 | + | resp = urllib.request.urlopen(req, timeout=30) | |
| 30 | + | ||
| 31 | + | content_type = resp.headers.get("Content-Type", "") | |
| 32 | + | if "application/json" in content_type: | |
| 33 | + | return json.loads(resp.read().decode()) | |
| 34 | + | else: | |
| 35 | + | return resp.read().decode() | |
| 36 | + | except Exception as e: | |
| 37 | + | return {"error": str(e)} | |
| 38 | + | ||
| 39 | + | @register_tool( | |
| 40 | + | "context7_search", | |
| 41 | + | "Search for libraries by name (e.g., 'react', 'express', 'django')", | |
| 42 | + | {"query": "string"} | |
| 43 | + | ) | |
| 44 | + | def context7_search(args): | |
| 45 | + | """Search for libraries in Context7""" | |
| 46 | + | query = args.get("query", "") | |
| 47 | + | if not query: | |
| 48 | + | return "error: query required" | |
| 49 | + | ||
| 50 | + | result = context7_get("/v1/search", {"query": query}) | |
| 51 | + | ||
| 52 | + | if isinstance(result, dict) and "error" in result: | |
| 53 | + | return f"error: {result['error']}" | |
| 54 | + | ||
| 55 | + | if isinstance(result, dict): | |
| 56 | + | results = result.get("results", []) | |
| 57 | + | else: | |
| 58 | + | return f"error: Unexpected response format" | |
| 59 | + | ||
| 60 | + | if not results: | |
| 61 | + | return f"No libraries found for: {query}" | |
| 62 | + | ||
| 63 | + | # Format results | |
| 64 | + | output = f"Found {len(results)} libraries:\n\n" | |
| 65 | + | for lib in results[:5]: # Top 5 | |
| 66 | + | output += f"• {lib.get('title', 'Unknown')}\n" | |
| 67 | + | output += f" ID: {lib.get('id', 'N/A')}\n" | |
| 68 | + | if lib.get('description'): | |
| 69 | + | output += f" {lib['description']}\n" | |
| 70 | + | output += f" Snippets: {lib.get('totalSnippets', 0)}\n" | |
| 71 | + | output += f" Trust Score: {lib.get('trustScore', 0)}\n" | |
| 72 | + | if lib.get('versions'): | |
| 73 | + | output += f" Versions: {', '.join(lib['versions'][:3])}\n" | |
| 74 | + | output += "\n" | |
| 75 | + | ||
| 76 | + | # Suggest using the top result | |
| 77 | + | if results: | |
| 78 | + | top_lib = results[0] | |
| 79 | + | lib_id = top_lib.get('id', '') | |
| 80 | + | output += f"To get documentation:\ncontext7_docs({{\"library_id\": \"{lib_id}\", \"topic\": \"your topic\"}})" | |
| 81 | + | ||
| 82 | + | return output | |
| 83 | + | ||
| 84 | + | @register_tool( | |
| 85 | + | "context7_docs", | |
| 86 | + | "Get documentation for a library using its Context7 ID", | |
| 87 | + | {"library_id": "string", "topic": "string?", "tokens": "number?"} | |
| 88 | + | ) | |
| 89 | + | def context7_docs(args): | |
| 90 | + | """Get library documentation from Context7""" | |
| 91 | + | library_id = args.get("library_id", "") | |
| 92 | + | topic = args.get("topic", "") | |
| 93 | + | tokens = args.get("tokens", 5000) | |
| 94 | + | ||
| 95 | + | if not library_id: | |
| 96 | + | return "error: library_id required (use context7_search first)" | |
| 97 | + | ||
| 98 | + | # Ensure minimum tokens | |
| 99 | + | if tokens < 1000: | |
| 100 | + | tokens = 1000 | |
| 101 | + | ||
| 102 | + | params = { | |
| 103 | + | "tokens": tokens, | |
| 104 | + | "type": "txt" | |
| 105 | + | } | |
| 106 | + | if topic: | |
| 107 | + | params["topic"] = topic | |
| 108 | + | ||
| 109 | + | result = context7_get(f"/v1{library_id}", params) | |
| 110 | + | ||
| 111 | + | if isinstance(result, dict) and "error" in result: | |
| 112 | + | return f"error: {result['error']}" | |
| 113 | + | ||
| 114 | + | if not result or result == "null": | |
| 115 | + | return f"No documentation found for {library_id}. Try using context7_search first." | |
| 116 | + | ||
| 117 | + | return result | |