liusijin revised this gist 1 month ago. Go to revision
1 file changed, 20 insertions, 53 deletions
DeepWiki.py
| @@ -3,50 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | 4 | import json | |
| 5 | 5 | import urllib.request | |
| 6 | + | import ssl | |
| 6 | 7 | import time | |
| 7 | 8 | ||
| 8 | - | # Global session cache | |
| 9 | - | _session_id = None | |
| 10 | - | ||
| 11 | - | def get_session_id(): | |
| 12 | - | """Initialize MCP session and get session ID""" | |
| 13 | - | global _session_id | |
| 14 | - | if _session_id: | |
| 15 | - | return _session_id | |
| 16 | - | ||
| 17 | - | url = "https://mcp.deepwiki.com/mcp" | |
| 18 | - | data = { | |
| 19 | - | "jsonrpc": "2.0", | |
| 20 | - | "id": "init", | |
| 21 | - | "method": "initialize", | |
| 22 | - | "params": { | |
| 23 | - | "protocolVersion": "2024-11-05", | |
| 24 | - | "capabilities": {}, | |
| 25 | - | "clientInfo": {"name": "nanocode", "version": "1.0"} | |
| 26 | - | } | |
| 27 | - | } | |
| 28 | - | ||
| 29 | - | try: | |
| 30 | - | req = urllib.request.Request( | |
| 31 | - | url, | |
| 32 | - | json.dumps(data).encode(), | |
| 33 | - | { | |
| 34 | - | "Content-Type": "application/json", | |
| 35 | - | "Accept": "application/json, text/event-stream" | |
| 36 | - | } | |
| 37 | - | ) | |
| 38 | - | resp = urllib.request.urlopen(req, timeout=30) | |
| 39 | - | _session_id = resp.headers.get("mcp-session-id") | |
| 40 | - | return _session_id | |
| 41 | - | except Exception as e: | |
| 42 | - | return None | |
| 43 | - | ||
| 44 | 9 | def mcp_call(tool_name, arguments): | |
| 45 | 10 | """Make MCP JSON-RPC call to DeepWiki""" | |
| 46 | - | session_id = get_session_id() | |
| 47 | - | if not session_id: | |
| 48 | - | return "error: Failed to initialize MCP session" | |
| 49 | - | ||
| 50 | 11 | url = "https://mcp.deepwiki.com/mcp" | |
| 51 | 12 | request_id = str(int(time.time() * 1000)) | |
| 52 | 13 | ||
| @@ -61,16 +22,20 @@ def mcp_call(tool_name, arguments): | |||
| 61 | 22 | } | |
| 62 | 23 | ||
| 63 | 24 | try: | |
| 25 | + | # Create SSL context that doesn't verify certificates for mitmproxy | |
| 26 | + | ssl_context = ssl.create_default_context() | |
| 27 | + | ssl_context.check_hostname = False | |
| 28 | + | ssl_context.verify_mode = ssl.CERT_NONE | |
| 29 | + | ||
| 64 | 30 | req = urllib.request.Request( | |
| 65 | 31 | url, | |
| 66 | 32 | json.dumps(data).encode(), | |
| 67 | 33 | { | |
| 68 | 34 | "Content-Type": "application/json", | |
| 69 | - | "Accept": "application/json, text/event-stream", | |
| 70 | - | "mcp-session-id": session_id | |
| 35 | + | "Accept": "application/json, text/event-stream" | |
| 71 | 36 | } | |
| 72 | 37 | ) | |
| 73 | - | resp = urllib.request.urlopen(req, timeout=30) | |
| 38 | + | resp = urllib.request.urlopen(req, timeout=60, context=ssl_context) | |
| 74 | 39 | ||
| 75 | 40 | # Handle SSE response | |
| 76 | 41 | response_text = resp.read().decode() | |
| @@ -78,28 +43,30 @@ def mcp_call(tool_name, arguments): | |||
| 78 | 43 | # Parse SSE format: extract JSON from "data: " lines | |
| 79 | 44 | for line in response_text.split('\n'): | |
| 80 | 45 | if line.startswith('data: '): | |
| 81 | - | data = line[6:].strip() # Skip "data: " prefix | |
| 46 | + | data_str = line[6:].strip() # Skip "data: " prefix | |
| 82 | 47 | ||
| 83 | 48 | # Skip ping events | |
| 84 | - | if data == "ping": | |
| 49 | + | if data_str == "ping": | |
| 85 | 50 | continue | |
| 86 | 51 | ||
| 87 | 52 | try: | |
| 88 | - | result = json.loads(data) | |
| 53 | + | result = json.loads(data_str) | |
| 89 | 54 | ||
| 90 | 55 | if "error" in result: | |
| 91 | - | return f"error: {result['error']['message']}" | |
| 56 | + | return f"error: {result['error'].get('message', str(result['error']))}" | |
| 92 | 57 | ||
| 93 | 58 | # Extract text from MCP response | |
| 94 | 59 | content = result.get("result", {}).get("content", []) | |
| 95 | 60 | if content: | |
| 96 | - | return "\n".join(item["text"] for item in content if item.get("type") == "text") | |
| 61 | + | texts = [item["text"] for item in content if item.get("type") == "text"] | |
| 62 | + | if texts: | |
| 63 | + | return "\n".join(texts) | |
| 97 | 64 | except json.JSONDecodeError: | |
| 98 | 65 | continue | |
| 99 | 66 | ||
| 100 | - | return "error: No data in response" | |
| 67 | + | return "error: No valid data in response" | |
| 101 | 68 | except Exception as e: | |
| 102 | - | return f"error: {e}" | |
| 69 | + | return f"error: {type(e).__name__}: {e}" | |
| 103 | 70 | ||
| 104 | 71 | @register_tool( | |
| 105 | 72 | "deepwiki_ask", | |
| @@ -107,7 +74,7 @@ def mcp_call(tool_name, arguments): | |||
| 107 | 74 | {"repo": "string", "question": "string"} | |
| 108 | 75 | ) | |
| 109 | 76 | def deepwiki_ask(args): | |
| 110 | - | """Ask a question about a GitHub repository""" | |
| 77 | + | """Ask a question about a GitHub repository (format: owner/repo)""" | |
| 111 | 78 | return mcp_call("ask_question", { | |
| 112 | 79 | "repoName": args["repo"], | |
| 113 | 80 | "question": args["question"] | |
| @@ -119,7 +86,7 @@ def deepwiki_ask(args): | |||
| 119 | 86 | {"repo": "string"} | |
| 120 | 87 | ) | |
| 121 | 88 | def deepwiki_structure(args): | |
| 122 | - | """Get documentation structure/topics for a GitHub repository""" | |
| 89 | + | """Get documentation structure/topics for a GitHub repository (format: owner/repo)""" | |
| 123 | 90 | return mcp_call("read_wiki_structure", { | |
| 124 | 91 | "repoName": args["repo"] | |
| 125 | 92 | }) | |
| @@ -130,7 +97,7 @@ def deepwiki_structure(args): | |||
| 130 | 97 | {"repo": "string"} | |
| 131 | 98 | ) | |
| 132 | 99 | def deepwiki_contents(args): | |
| 133 | - | """Get full documentation contents for a GitHub repository""" | |
| 100 | + | """Get full documentation contents for a GitHub repository (format: owner/repo)""" | |
| 134 | 101 | return mcp_call("read_wiki_contents", { | |
| 135 | 102 | "repoName": args["repo"] | |
| 136 | 103 | }) | |
liusijin revised this gist 1 month ago. Go to revision
No changes
liusijin revised this gist 2 months ago. Go to revision
No changes
liusijin revised this gist 2 months ago. Go to revision
No changes
liusijin revised this gist 2 months ago. Go to revision
No changes
liusijin revised this gist 2 months ago. Go to revision
No changes
liusijin revised this gist 2 months ago. Go to revision
1 file changed, 136 insertions
DeepWiki.py(file created)
| @@ -0,0 +1,136 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 2 | + | """DeepWiki extension - MCP HTTP client for GitHub repo documentation""" | |
| 3 | + | ||
| 4 | + | import json | |
| 5 | + | import urllib.request | |
| 6 | + | import time | |
| 7 | + | ||
| 8 | + | # Global session cache | |
| 9 | + | _session_id = None | |
| 10 | + | ||
| 11 | + | def get_session_id(): | |
| 12 | + | """Initialize MCP session and get session ID""" | |
| 13 | + | global _session_id | |
| 14 | + | if _session_id: | |
| 15 | + | return _session_id | |
| 16 | + | ||
| 17 | + | url = "https://mcp.deepwiki.com/mcp" | |
| 18 | + | data = { | |
| 19 | + | "jsonrpc": "2.0", | |
| 20 | + | "id": "init", | |
| 21 | + | "method": "initialize", | |
| 22 | + | "params": { | |
| 23 | + | "protocolVersion": "2024-11-05", | |
| 24 | + | "capabilities": {}, | |
| 25 | + | "clientInfo": {"name": "nanocode", "version": "1.0"} | |
| 26 | + | } | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | try: | |
| 30 | + | req = urllib.request.Request( | |
| 31 | + | url, | |
| 32 | + | json.dumps(data).encode(), | |
| 33 | + | { | |
| 34 | + | "Content-Type": "application/json", | |
| 35 | + | "Accept": "application/json, text/event-stream" | |
| 36 | + | } | |
| 37 | + | ) | |
| 38 | + | resp = urllib.request.urlopen(req, timeout=30) | |
| 39 | + | _session_id = resp.headers.get("mcp-session-id") | |
| 40 | + | return _session_id | |
| 41 | + | except Exception as e: | |
| 42 | + | return None | |
| 43 | + | ||
| 44 | + | def mcp_call(tool_name, arguments): | |
| 45 | + | """Make MCP JSON-RPC call to DeepWiki""" | |
| 46 | + | session_id = get_session_id() | |
| 47 | + | if not session_id: | |
| 48 | + | return "error: Failed to initialize MCP session" | |
| 49 | + | ||
| 50 | + | url = "https://mcp.deepwiki.com/mcp" | |
| 51 | + | request_id = str(int(time.time() * 1000)) | |
| 52 | + | ||
| 53 | + | data = { | |
| 54 | + | "jsonrpc": "2.0", | |
| 55 | + | "id": request_id, | |
| 56 | + | "method": "tools/call", | |
| 57 | + | "params": { | |
| 58 | + | "name": tool_name, | |
| 59 | + | "arguments": arguments | |
| 60 | + | } | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | try: | |
| 64 | + | req = urllib.request.Request( | |
| 65 | + | url, | |
| 66 | + | json.dumps(data).encode(), | |
| 67 | + | { | |
| 68 | + | "Content-Type": "application/json", | |
| 69 | + | "Accept": "application/json, text/event-stream", | |
| 70 | + | "mcp-session-id": session_id | |
| 71 | + | } | |
| 72 | + | ) | |
| 73 | + | resp = urllib.request.urlopen(req, timeout=30) | |
| 74 | + | ||
| 75 | + | # Handle SSE response | |
| 76 | + | response_text = resp.read().decode() | |
| 77 | + | ||
| 78 | + | # Parse SSE format: extract JSON from "data: " lines | |
| 79 | + | for line in response_text.split('\n'): | |
| 80 | + | if line.startswith('data: '): | |
| 81 | + | data = line[6:].strip() # Skip "data: " prefix | |
| 82 | + | ||
| 83 | + | # Skip ping events | |
| 84 | + | if data == "ping": | |
| 85 | + | continue | |
| 86 | + | ||
| 87 | + | try: | |
| 88 | + | result = json.loads(data) | |
| 89 | + | ||
| 90 | + | if "error" in result: | |
| 91 | + | return f"error: {result['error']['message']}" | |
| 92 | + | ||
| 93 | + | # Extract text from MCP response | |
| 94 | + | content = result.get("result", {}).get("content", []) | |
| 95 | + | if content: | |
| 96 | + | return "\n".join(item["text"] for item in content if item.get("type") == "text") | |
| 97 | + | except json.JSONDecodeError: | |
| 98 | + | continue | |
| 99 | + | ||
| 100 | + | return "error: No data in response" | |
| 101 | + | except Exception as e: | |
| 102 | + | return f"error: {e}" | |
| 103 | + | ||
| 104 | + | @register_tool( | |
| 105 | + | "deepwiki_ask", | |
| 106 | + | "Ask questions about GitHub repo", | |
| 107 | + | {"repo": "string", "question": "string"} | |
| 108 | + | ) | |
| 109 | + | def deepwiki_ask(args): | |
| 110 | + | """Ask a question about a GitHub repository""" | |
| 111 | + | return mcp_call("ask_question", { | |
| 112 | + | "repoName": args["repo"], | |
| 113 | + | "question": args["question"] | |
| 114 | + | }) | |
| 115 | + | ||
| 116 | + | @register_tool( | |
| 117 | + | "deepwiki_structure", | |
| 118 | + | "Get repo documentation structure", | |
| 119 | + | {"repo": "string"} | |
| 120 | + | ) | |
| 121 | + | def deepwiki_structure(args): | |
| 122 | + | """Get documentation structure/topics for a GitHub repository""" | |
| 123 | + | return mcp_call("read_wiki_structure", { | |
| 124 | + | "repoName": args["repo"] | |
| 125 | + | }) | |
| 126 | + | ||
| 127 | + | @register_tool( | |
| 128 | + | "deepwiki_contents", | |
| 129 | + | "Get repo documentation contents", | |
| 130 | + | {"repo": "string"} | |
| 131 | + | ) | |
| 132 | + | def deepwiki_contents(args): | |
| 133 | + | """Get full documentation contents for a GitHub repository""" | |
| 134 | + | return mcp_call("read_wiki_contents", { | |
| 135 | + | "repoName": args["repo"] | |
| 136 | + | }) | |