search_code
Search for a keyword across all loaded scripts or within a single script. Returns matches with surrounding context, supporting both line and character modes.
Instructions
Search keyword in loaded scripts (v0.9.0 unified).
Replaces search_code (all scripts) + search_code_in_script (single script).
Args: keyword: The keyword to search for (case-sensitive substring match). script_url: If None, search across ALL loaded scripts. If given, search within that one script only (supports "inline:" for inline scripts). Single-script mode auto-detects minified files and uses character-based context. context_chars: Context window in char mode (default 200 = +/-200 chars). Used when searching single minified scripts. context_lines: Context window in line mode (default 3). max_results: Maximum matches to return (default 200).
Returns: dict with matches, total_matches, mode ("line" | "char"), etc.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | ||
| script_url | No | ||
| context_chars | No | ||
| context_lines | No | ||
| max_results | No |
Implementation Reference
- The MCP tool handler function 'search_code' — decorated with @mcp.tool(). Dispatches to either _search_code_all (all scripts) or _search_code_in_script (single script). Accepts keyword, script_url, context_chars, context_lines, max_results.
@mcp.tool() async def search_code( keyword: str, script_url: str | None = None, context_chars: int = 200, context_lines: int = 3, max_results: int = 200, ) -> dict: """Search keyword in loaded scripts (v0.9.0 unified). Replaces search_code (all scripts) + search_code_in_script (single script). Args: keyword: The keyword to search for (case-sensitive substring match). script_url: If None, search across ALL loaded scripts. If given, search within that one script only (supports "inline:<index>" for inline scripts). Single-script mode auto-detects minified files and uses character-based context. context_chars: Context window in char mode (default 200 = +/-200 chars). Used when searching single minified scripts. context_lines: Context window in line mode (default 3). max_results: Maximum matches to return (default 200). Returns: dict with matches, total_matches, mode ("line" | "char"), etc. """ if script_url is None: return await _search_code_all(keyword, max_results) else: return await _search_code_in_script( script_url, keyword, context_lines, context_chars, max_results ) - src/camoufox_reverse_mcp/tools/script_analysis.py:50-51 (registration)Registration via @mcp.tool() decorator on line 50. The module is imported in server.py line 16, which triggers the decorator and registers the tool with the FastMCP server.
@mcp.tool() async def search_code( - Internal helper _search_code_all: searches keyword across ALL loaded scripts on the page via page.evaluate(). Returns matches with line context (line-mode), capped at max_results (default 50, max 200).
async def _search_code_all(keyword: str, max_results: int = 50) -> dict: try: if max_results > 200: max_results = 200 page = await browser_manager.get_active_page() results = await page.evaluate(f"""async () => {{ const keyword = {repr(keyword)}; const scripts = document.querySelectorAll('script'); const matches = []; const maxResults = {max_results}; let totalMatches = 0; let scriptsSearched = 0; const scriptsWithMatches = []; for (const s of scripts) {{ let source = ''; let scriptUrl = ''; if (s.src) {{ scriptUrl = s.src; try {{ const resp = await fetch(s.src); source = await resp.text(); }} catch(e) {{ continue; }} }} else {{ scriptUrl = 'inline:' + scriptsSearched; source = s.textContent || ''; }} scriptsSearched++; const lines = source.split('\\n'); let scriptMatchCount = 0; for (let i = 0; i < lines.length; i++) {{ if (lines[i].includes(keyword)) {{ totalMatches++; scriptMatchCount++; if (matches.length < maxResults) {{ const start = Math.max(0, i - 2); const end = Math.min(lines.length, i + 3); const contextLines = lines.slice(start, end); const contextStr = contextLines.join('\\n'); matches.push({{ script_url: scriptUrl, line_number: i + 1, match: lines[i].trim().substring(0, 500), context: contextStr.length > 2000 ? contextStr.substring(0, 2000) + '...(truncated)' : contextStr }}); }} }} }} if (scriptMatchCount > 0) {{ scriptsWithMatches.push({{ url: scriptUrl, match_count: scriptMatchCount, source_length: source.length }}); }} }} return {{ matches: matches, total_matches: totalMatches, returned_matches: matches.length, scripts_searched: scriptsSearched, scripts_with_matches: scriptsWithMatches, truncated: totalMatches > matches.length }}; }}""") return results except Exception as e: return {"error": str(e)} - Internal helper _search_code_in_script: searches keyword within a single script. Auto-detects minified files (char mode vs line mode). For char mode provides position/context_range; for line mode provides line numbers with context_lines.
async def _search_code_in_script( script_url: str, keyword: str, context_lines: int = 3, context_chars: int = 200, max_results: int = 200, ) -> dict: try: page = await browser_manager.get_active_page() if script_url.startswith("inline:"): idx = int(script_url.split(":")[1]) src = await page.evaluate(f"""() => {{ const scripts = document.querySelectorAll('script'); return scripts[{idx}] ? (scripts[{idx}].textContent || '') : null; }}""") if src is None: return {"error": f"Inline script not found at index {idx}"} else: src = await page.evaluate( f"fetch({_json.dumps(script_url)}, {{cache: 'force-cache'}}).then(r => r.text())" ) if not isinstance(src, str): return {"error": f"script not fetchable: got {type(src).__name__}"} lines = src.split("\n") max_line_len = max((len(l) for l in lines), default=0) use_char_mode = len(lines) < 10 or max_line_len > 5000 results: list[dict] = [] total = 0 if use_char_mode: i = 0 while True: pos = src.find(keyword, i) if pos == -1: break total += 1 if len(results) < max_results: start = max(0, pos - context_chars) end = min(len(src), pos + len(keyword) + context_chars) results.append({ "position": pos, "context_start": start, "context_end": end, "context": src[start:end], "match_highlight_range": [pos - start, pos - start + len(keyword)], }) i = pos + len(keyword) return { "total_matches": total, "returned": len(results), "script_url": script_url, "mode": "char", "source_size": len(src), "total_lines": len(lines), "max_line_length": max_line_len, "context_chars": context_chars, "results": results, } for idx, line in enumerate(lines): if keyword in line: total += 1 if len(results) < max_results: start = max(0, idx - context_lines) end = min(len(lines), idx + context_lines + 1) ctx = "\n".join(lines[start:end]) results.append({ "line": idx + 1, "context": ctx[:3000] + ("...(truncated)" if len(ctx) > 3000 else ""), "context_range": [start + 1, end], }) return { "total_matches": total, "returned": len(results), "script_url": script_url, "mode": "line", "total_lines": len(lines), "results": results, } except Exception as e: return {"error": str(e)} - The schema/type signature of the tool: keyword (str, required), script_url (str|None), context_chars (int, default 200), context_lines (int, default 3), max_results (int, default 200). Returns dict.
@mcp.tool() async def search_code( keyword: str, script_url: str | None = None, context_chars: int = 200, context_lines: int = 3, max_results: int = 200, ) -> dict: