Complete Guide: Building an AI Knowledge Companion with Browser-Use, MCP, and Ollama for Advanced Web Automation and Information Processing
A comprehensive guide to building an AI-powered knowledge companion system that combines Browser-Use for web automation, Model Context Protocol (MCP) for tool integration, and Ollama for local LLM processing to create an intelligent research and information gathering assistant.
Daniel Kliewer
Author, Sovereign AI


Beyond Research: Building a Modern AI Knowledge Companion
A Comprehensive Guide to Browser-Use, MCP, and AI-Powered Information Processing
1. Introduction to AI-Powered Knowledge Systems
In today's information landscape, the ability to efficiently gather, process, and synthesize knowledge has become essential. This guide transforms the concept of a basic research assistant into a comprehensive AI Knowledge Companion system—a versatile tool that not only conducts research but acts as your digital extension in navigating the vast information ecosystem.
What is Browser-Use? Browser-Use is a programmable interface that enables AI systems to interact with web browsers just as humans do—visiting websites, clicking links, filling forms, and extracting information. Unlike simple web scraping, Browser-Use provides true browser automation that can handle modern, JavaScript-heavy websites, captchas, and complex user interactions.
What is MCP (Model Context Protocol)? The Model Context Protocol is a standardized framework that facilitates secure communication between AI models and external tools or data sources. MCP defines how information is exchanged, permissions are granted, and results are returned, creating a universal "language" for AI systems to safely and effectively interface with the digital world.
2. Understanding the Core Technologies
Browser-Use: AI's Window to the Web
Browser-Use fundamentally transforms how AI interacts with the internet by:
- Providing visual context: Unlike API-based approaches, Browser-Use allows the AI to "see" what a human would see
- Enabling stateful navigation: Maintaining session information across multiple pages
- Handling dynamic content: Processing JavaScript-rendered pages that traditional scrapers cannot access
- Supporting authentication: Logging into services when needed
Implementation principle: Browser-Use creates a controlled browser instance that executes commands from your AI system through a dedicated interface, while feeding back visual and structural information about the pages it visits.
MCP: The Universal AI Connector
MCP serves as a standardized protocol for AI-to-tool communication, addressing several key challenges:
- Security: Defining clear permission boundaries and data access controls
- Interoperability: Creating a common language for diverse tools to connect to AI systems
- Context management: Efficiently transferring relevant information between systems
- Versioning and compatibility: Ensuring tools and AI models can evolve independently
Key concept: MCP treats external tools as "contexts" that an AI model can access, defining both how the AI can request information and how the external systems should respond.
3. Project Architecture: Building Your Knowledge Companion
System Overview
Our Knowledge Companion consists of five core components:
- User Interface: Accepts queries and displays results
- Orchestration Engine: Coordinates all system components
- LLM Core: Processes language, plans actions, and generates reports
- Browser-Use Module: Handles web navigation and extraction
- MCP Integration Layer: Connects to external knowledge sources
Component Interaction Flow
- User submits a query through the interface
- The orchestration engine passes the query to the LLM core
- The LLM plans a research strategy and generates actions
- Actions are executed through Browser-Use or MCP connections
- Retrieved information returns to the LLM for synthesis
- The final report is presented to the user
Design philosophy: This modular architecture allows each component to evolve independently while maintaining clear communication channels between them.
4. Setting Up Your Development Environment
Hardware and Software Requirements
For optimal performance, we recommend:
- CPU: 4+ cores (8+ preferred)
- RAM: 16GB minimum (32GB recommended)
- Storage: 20GB free space (SSD preferred)
- GPU: Optional but beneficial for larger models
- Operating System: Linux, macOS, or Windows 10/11
Installation Process
- Python Environment Setup:
bash1# Create a virtual environment2python -m venv ai-companion3source ai-companion/bin/activate # On Windows: ai-companion\Scripts\activate45# Install core dependencies6pip install browser-use ollama mcp-client pydantic fastapi uvicorn
- Ollama Configuration:
bash1# Download Ollama from https://ollama.com2# Then pull the Llama 3.2 model3ollama pull llama3.245# Test the model6ollama run llama3.2 "Hello, world!"
- Browser-Use Setup:
python1# Test browser-use functionality2from browser_use import BrowserSession34browser = BrowserSession()5browser.navigate("https://www.example.com")6content = browser.get_page_content()7print(content)8browser.close()
- MCP Configuration:
python1# Configure MCP client2from mcp_client import MCPClient34mcp = MCPClient(5 server_url="https://your-mcp-server.com",6 api_key="your_api_key",7 default_timeout=308)910# Test connection11status = mcp.check_connection()12print(f"MCP Connection: {status}")
Important concept: The separation between the LLM runtime (Ollama) and your application code creates a clean architecture that can adapt to different models and execution environments.
5. Implementing Browser-Use Intelligence
Understanding Browser Automation Principles
When implementing Browser-Use, it's essential to understand that we're creating an AI system that can:
- Form intentions: Decide what information to seek
- Execute navigation: Move through websites purposefully
- Extract information: Identify and collect relevant data
- Process results: Transform raw web content into structured knowledge
Creating a Robust Browser-Use Module
python1class IntelligentBrowser:2 def __init__(self, headless=True):3 """Initialize browser session with configurable visibility."""4 self.browser = BrowserSession(headless=headless)5 self.history = []67 def search(self, query, search_engine="google"):8 """Perform a search using specified engine."""9 if search_engine == "google":10 self.browser.navigate("https://www.google.com")11 search_box = self.browser.find_element('input[name="q"]')12 self.browser.input_text(search_box, query)13 self.browser.press_enter()14 self.history.append({"action": "search", "query": query})15 return self.get_search_results()1617 def get_search_results(self):18 """Extract search results from the current page."""19 results = []20 elements = self.browser.find_elements("div.g")2122 for element in elements:23 title_elem = self.browser.find_element_within(element, "h3")24 link_elem = self.browser.find_element_within(element, "a")25 snippet_elem = self.browser.find_element_within(element, "div.VwiC3b")2627 if title_elem and link_elem and snippet_elem:28 title = self.browser.get_text(title_elem)29 link = self.browser.get_attribute(link_elem, "href")30 snippet = self.browser.get_text(snippet_elem)3132 results.append({33 "title": title,34 "url": link,35 "snippet": snippet36 })3738 return results3940 def visit_page(self, url):41 """Navigate to a specific URL and extract content."""42 self.browser.navigate(url)43 self.history.append({"action": "visit", "url": url})4445 # Wait for page to load completely46 self.browser.wait_for_page_load()4748 # Extract main content, avoiding navigation elements49 content = self.extract_main_content()50 return {51 "url": url,52 "title": self.browser.get_page_title(),53 "content": content54 }5556 def extract_main_content(self):57 """Intelligently extract the main content from the current page."""58 # Try common content selectors59 content_selectors = [60 "article", "main", ".content", "#content",61 "[role='main']", ".post-content"62 ]6364 for selector in content_selectors:65 element = self.browser.find_element(selector)66 if element:67 return self.browser.get_text(element)6869 # Fallback: use heuristics to find the largest text block70 paragraphs = self.browser.find_elements("p")71 if paragraphs:72 paragraph_texts = [self.browser.get_text(p) for p in paragraphs]73 # Filter out very short paragraphs74 substantial_paragraphs = [p for p in paragraph_texts if len(p) > 100]75 if substantial_paragraphs:76 return "\n\n".join(substantial_paragraphs)7778 # Last resort: get body text79 return self.browser.get_body_text()8081 def close(self):82 """Close the browser session."""83 self.browser.close()
Key insight: The above implementation demonstrates how Browser-Use goes beyond simple scraping by making contextual decisions about what content is relevant, handling different site structures, and maintaining state across navigation.
6. Implementing the MCP Integration Layer
Understanding the Model Context Protocol
MCP enables standardized communication between your AI system and external tools through a structured protocol. Instead of custom code for each integration, MCP provides a unified framework for:
- Tool registration: Defining what tools are available
- Request formatting: Structuring how the AI requests information
- Response handling: Processing and validating tool outputs
- Error management: Handling failures in a consistent way
Building an MCP Client
python1class KnowledgeSourceManager:2 def __init__(self, mcp_client):3 """Initialize with an MCP client."""4 self.mcp = mcp_client5 self.available_sources = self._discover_sources()67 def _discover_sources(self):8 """Query the MCP server for available knowledge sources."""9 try:10 sources = self.mcp.list_contexts()11 return {12 source["name"]: {13 "description": source["description"],14 "capabilities": source["capabilities"],15 "parameters": source["parameters"]16 } for source in sources17 }18 except Exception as e:19 print(f"Error discovering sources: {e}")20 return {}2122 def query_source(self, source_name, query_params):23 """Query a specific knowledge source through MCP."""24 if source_name not in self.available_sources:25 raise ValueError(f"Unknown source: {source_name}")2627 try:28 response = self.mcp.query_context(29 context_name=source_name,30 parameters=query_params31 )32 return response33 except Exception as e:34 print(f"Error querying {source_name}: {e}")35 return {"error": str(e)}3637 def search_arxiv(self, query, max_results=5, categories=None):38 """Specialized method for arXiv searches."""39 params = {40 "query": query,41 "max_results": max_results42 }4344 if categories:45 params["categories"] = categories4647 return self.query_source("arxiv", params)4849 def search_wikipedia(self, query, depth=1):50 """Specialized method for Wikipedia searches."""51 params = {52 "query": query,53 "depth": depth # How many links to follow54 }5556 return self.query_source("wikipedia", params)5758 def get_source_capabilities(self, source_name):59 """Get detailed information about a knowledge source."""60 if source_name in self.available_sources:61 return self.available_sources[source_name]62 return None
MCP concept in practice: This implementation shows how MCP creates a uniform interface to diverse knowledge sources. The AI doesn't need to know the specifics of the arXiv API or Wikipedia's structure—it just makes standardized requests through the MCP protocol.
7. The Orchestration Engine: Coordinating Your AI System
Understanding Orchestration
The orchestration engine is the "brain" of your Knowledge Companion, responsible for:
- Query analysis: Understanding what the user is asking
- Planning: Determining which tools to use and in what sequence
- Execution: Calling the appropriate components
- Integration: Combining information from multiple sources
- Presentation: Formatting the final output for the user
Implementing the Orchestrator
python1class KnowledgeOrchestrator:2 def __init__(self, llm_client, browser, knowledge_manager):3 """Initialize with core components."""4 self.llm = llm_client5 self.browser = browser6 self.knowledge_manager = knowledge_manager78 async def process_query(self, user_query):9 """Process a user query from start to finish."""10 # Step 1: Analyze the query to determine approach11 analysis = await self._analyze_query(user_query)1213 # Step 2: Execute the research plan14 research_results = await self._execute_research_plan(analysis, user_query)1516 # Step 3: Synthesize the findings into a coherent response17 final_report = await self._synthesize_report(user_query, research_results)1819 return final_report2021 async def _analyze_query(self, query):22 """Use the LLM to analyze the query and create a research plan."""23 prompt = f"""24 Analyze the following research query and determine the best approach:2526 QUERY: {query}2728 Please determine:29 1. What type of information is being requested?30 2. Which knowledge sources would be most relevant (web search, arXiv, Wikipedia, etc.)?31 3. What specific search terms should be used for each source?32 4. What is the priority order for consulting these sources?33 5. Are there any specialized domains or technical knowledge required?3435 Return your analysis as a structured JSON object.36 """3738 response = await self.llm.complete(prompt)39 return json.loads(response)4041 async def _execute_research_plan(self, analysis, original_query):42 """Execute the research plan across multiple sources."""43 results = []4445 # Execute based on the priority order determined in the analysis46 for source in analysis["priority_order"]:47 if source == "web_search":48 search_terms = analysis["search_terms"]["web_search"]49 web_results = await self._perform_web_research(search_terms)50 results.append({51 "source": "web_search",52 "data": web_results53 })5455 elif source == "arxiv":56 if "arxiv" in analysis["search_terms"]:57 arxiv_query = analysis["search_terms"]["arxiv"]58 categories = analysis.get("arxiv_categories", None)59 arxiv_results = self.knowledge_manager.search_arxiv(60 arxiv_query,61 categories=categories62 )63 results.append({64 "source": "arxiv",65 "data": arxiv_results66 })6768 elif source == "wikipedia":69 if "wikipedia" in analysis["search_terms"]:70 wiki_query = analysis["search_terms"]["wikipedia"]71 wiki_results = self.knowledge_manager.search_wikipedia(wiki_query)72 results.append({73 "source": "wikipedia",74 "data": wiki_results75 })7677 # If needed, perform follow-up research based on initial findings78 if analysis.get("requires_followup", False):79 followup_results = await self._perform_followup_research(results, original_query)80 results.extend(followup_results)8182 return results8384 async def _perform_web_research(self, search_terms):85 """Conduct web research using Browser-Use."""86 web_results = []87 for term in search_terms:88 # Search and get results89 search_results = self.browser.search(term)9091 # Visit the top 3 results and extract content92 for result in search_results[:3]:93 page_data = self.browser.visit_page(result["url"])94 web_results.append({95 "search_term": term,96 "page_data": page_data97 })9899 return web_results100101 async def _perform_followup_research(self, initial_results, original_query):102 """Conduct follow-up research based on initial findings."""103 # Generate follow-up questions using the LLM104 prompt = f"""105 Based on these initial research results and the original query:106107 ORIGINAL QUERY: {original_query}108109 INITIAL FINDINGS: {json.dumps(initial_results, indent=2)}110111 Generate 3 follow-up questions that would help complete the research.112 Format as a JSON list of questions.113 """114115 followup_response = await self.llm.complete(prompt)116 followup_questions = json.loads(followup_response)117118 followup_results = []119 for question in followup_questions:120 # Recursively analyze and research each follow-up question121 followup_analysis = await self._analyze_query(question)122 question_results = await self._execute_research_plan(followup_analysis, question)123 followup_results.append({124 "followup_question": question,125 "results": question_results126 })127128 return followup_results129130 async def _synthesize_report(self, original_query, research_results):131 """Synthesize research results into a comprehensive report."""132 prompt = f"""133 Create a comprehensive research report based on the following information:134135 ORIGINAL QUERY: {original_query}136137 RESEARCH FINDINGS: {json.dumps(research_results, indent=2)}138139 Your report should:140 1. Start with an executive summary141 2. Organize information logically by topic and source142 3. Highlight key findings and insights143 4. Note any contradictions or gaps in the research144 5. Include relevant citations to original sources145 6. End with conclusions and potential next steps146147 Format the report in Markdown for readability.148 """149150 report = await self.llm.complete(prompt)151 return report
Orchestration insight: The orchestrator demonstrates how to implement a multi-step research process that intelligently combines Browser-Use and MCP. Note how the system uses the LLM at multiple stages—for planning, for generating follow-up questions, and for synthesizing the final report.
8. Creating an Effective User Interface
Console-based Interface
For a simple but effective console interface:
python1import asyncio2import rich3from rich.console import Console4from rich.markdown import Markdown56console = Console()78class KnowledgeCompanionCLI:9 def __init__(self, orchestrator):10 self.orchestrator = orchestrator1112 async def start(self):13 console.print("[bold blue]AI Knowledge Companion[/bold blue]")14 console.print("Ask me anything, and I'll research it for you.")15 console.print("Type 'exit' to quit.\n")1617 while True:18 query = console.input("[bold green]Query:[/bold green] ")1920 if query.lower() in ('exit', 'quit'):21 break2223 console.print("\n[italic]Researching your query...[/italic]")2425 try:26 with console.status("[bold green]Thinking..."):27 report = await self.orchestrator.process_query(query)2829 console.print("\n[bold]Research Results:[/bold]\n")30 console.print(Markdown(report))3132 except Exception as e:33 console.print(f"[bold red]Error:[/bold red] {str(e)}")3435 console.print("[bold blue]Thank you for using AI Knowledge Companion![/bold blue]")3637# Usage38async def main():39 # Setup components (simplified)40 llm_client = LlamaClient()41 browser = IntelligentBrowser()42 mcp_client = MCPClient(server_url="https://mcp.example.com")43 knowledge_manager = KnowledgeSourceManager(mcp_client)44 orchestrator = KnowledgeOrchestrator(llm_client, browser, knowledge_manager)4546 # Start the CLI47 cli = KnowledgeCompanionCLI(orchestrator)48 await cli.start()4950 # Cleanup51 browser.close()5253if __name__ == "__main__":54 asyncio.run(main())
Web-based Interface (FastAPI)
For a more versatile web interface:
python1from fastapi import FastAPI, BackgroundTasks2from pydantic import BaseModel3from typing import Optional, List4import uvicorn56app = FastAPI(title="AI Knowledge Companion API")78# Data models9class Query(BaseModel):10 text: str11 max_sources: Optional[int] = 512 preferred_sources: Optional[List[str]] = None1314class ResearchStatus(BaseModel):15 query_id: str16 status: str17 progress: float18 message: Optional[str] = None1920class ResearchReport(BaseModel):21 query_id: str22 query_text: str23 report: str24 sources: List[dict]25 execution_time: float2627# In-memory storage for demo purposes28research_tasks = {}2930@app.post("/research/start", response_model=ResearchStatus)31async def start_research(query: Query, background_tasks: BackgroundTasks):32 """Start a new research task."""33 query_id = str(uuid.uuid4())3435 # Store initial status36 research_tasks[query_id] = {37 "status": "starting",38 "progress": 0.0,39 "query": query.text,40 "report": None41 }4243 # Launch research in background44 background_tasks.add_task(45 perform_research,46 query_id,47 query.text,48 query.max_sources,49 query.preferred_sources50 )5152 return ResearchStatus(53 query_id=query_id,54 status="starting",55 progress=0.0,56 message="Research task initiated"57 )5859@app.get("/research/{query_id}/status", response_model=ResearchStatus)60async def get_research_status(query_id: str):61 """Get the status of a research task."""62 if query_id not in research_tasks:63 raise HTTPException(status_code=404, detail="Research task not found")6465 task = research_tasks[query_id]66 return ResearchStatus(67 query_id=query_id,68 status=task["status"],69 progress=task["progress"],70 message=task.get("message")71 )7273@app.get("/research/{query_id}/report", response_model=ResearchReport)74async def get_research_report(query_id: str):75 """Get the final report of a completed research task."""76 if query_id not in research_tasks:77 raise HTTPException(status_code=404, detail="Research task not found")7879 task = research_tasks[query_id]8081 if task["status"] != "completed":82 raise HTTPException(status_code=400, detail="Research not yet completed")8384 return ResearchReport(85 query_id=query_id,86 query_text=task["query"],87 report=task["report"],88 sources=task["sources"],89 execution_time=task["execution_time"]90 )9192async def perform_research(query_id, query_text, max_sources, preferred_sources):93 """Background task to perform the actual research."""94 try:95 # Update status96 research_tasks[query_id]["status"] = "researching"97 research_tasks[query_id]["progress"] = 0.19899 # Create components (simplified)100 llm_client = LlamaClient()101 browser = IntelligentBrowser()102 mcp_client = MCPClient(server_url="https://mcp.example.com")103 knowledge_manager = KnowledgeSourceManager(mcp_client)104 orchestrator = KnowledgeOrchestrator(llm_client, browser, knowledge_manager)105106 # Update progress periodically107 research_tasks[query_id]["progress"] = 0.3108109 # Perform the research110 start_time = time.time()111 report = await orchestrator.process_query(query_text)112 end_time = time.time()113114 # Store the results115 research_tasks[query_id].update({116 "status": "completed",117 "progress": 1.0,118 "report": report,119 "sources": orchestrator.get_used_sources(),120 "execution_time": end_time - start_time121 })122123 # Cleanup124 browser.close()125126 except Exception as e:127 research_tasks[query_id].update({128 "status": "error",129 "message": str(e)130 })131132# Run with: uvicorn app:app --reload
UI design principle: Both interfaces demonstrate the importance of providing feedback during long-running research operations. The web interface adds asynchronous operation, allowing users to start research and check back later for results.
9. Advanced Features and Optimizations
Caching for Performance
Implement a caching layer to store frequently accessed information:
python1import hashlib2import json3import aioredis4from datetime import timedelta56class KnowledgeCache:7 def __init__(self, redis_url="redis://localhost"):8 """Initialize the caching system."""9 self.redis = None10 self.redis_url = redis_url1112 async def connect(self):13 """Connect to Redis."""14 self.redis = await aioredis.create_redis_pool(self.redis_url)1516 async def close(self):17 """Close Redis connection."""18 if self.redis:19 self.redis.close()20 await self.redis.wait_closed()2122 async def get_cached_result(self, query, source):23 """Try to get a cached result for a query from a specific source."""24 if not self.redis:25 return None2627 cache_key = self._make_cache_key(query, source)28 cached_data = await self.redis.get(cache_key)2930 if cached_data:31 return json.loads(cached_data)32 return None3334 async def cache_result(self, query, source, result, ttl=timedelta(hours=24)):35 """Cache a result with an expiration time."""36 if not self.redis:37 return3839 cache_key = self._make_cache_key(query, source)40 await self.redis.set(41 cache_key,42 json.dumps(result),43 expire=int(ttl.total_seconds())44 )4546 def _make_cache_key(self, query, source):47 """Create a deterministic cache key."""48 data = f"{query}:{source}"49 return f"knowledge_cache:{hashlib.md5(data.encode()).hexdigest()}"
Parallel Processing
Optimize research by executing multiple sources in parallel:
python1async def _execute_research_plan(self, analysis, original_query):2 """Execute the research plan with parallel processing."""3 tasks = []45 # Create tasks for each source in the research plan6 for source in analysis["priority_order"]:7 if source == "web_search" and "web_search" in analysis["search_terms"]:8 search_terms = analysis["search_terms"]["web_search"]9 task = asyncio.create_task(10 self._perform_web_research(search_terms)11 )12 tasks.append(("web_search", task))1314 elif source == "arxiv" and "arxiv" in analysis["search_terms"]:15 arxiv_query = analysis["search_terms"]["arxiv"]16 categories = analysis.get("arxiv_categories", None)17 task = asyncio.create_task(18 self._perform_arxiv_research(arxiv_query, categories)19 )20 tasks.append(("arxiv", task))2122 elif source == "wikipedia" and "wikipedia" in analysis["search_terms"]:23 wiki_query = analysis["search_terms"]["wikipedia"]24 task = asyncio.create_task(25 self._perform_wikipedia_research(wiki_query)26 )27 tasks.append(("wikipedia", task))2829 # Wait for all tasks to complete30 results = []31 for source_name, task in tasks:32 try:33 data = await task34 results.append({35 "source": source_name,36 "data": data37 })38 except Exception as e:39 print(f"Error researching {source_name}: {e}")40 results.append({41 "source": source_name,42 "error": str(e)43 })4445 # If needed, perform follow-up research based on initial findings46 if analysis.get("requires_followup", False):47 followup_results = await self._perform_followup_research(results, original_query)48 results.extend(followup_results)4950 return results
Smart Throttling
Prevent overloading external services:
python1class RateLimiter:2 def __init__(self):3 """Initialize rate limiters for different domains."""4 self.limiters = {}56 def register_domain(self, domain, requests_per_minute):7 """Register rate limits for a domain."""8 self.limiters[domain] = {9 "rate": requests_per_minute,10 "tokens": requests_per_minute,11 "last_update": time.time(),12 "lock": asyncio.Lock()13 }1415 async def acquire(self, url):16 """Acquire permission to make a request to a URL."""17 domain = self._extract_domain(url)1819 if domain not in self.limiters:20 # Default conservative limit21 self.register_domain(domain, 10)2223 limiter = self.limiters[domain]2425 async with limiter["lock"]:26 # Refill tokens based on time elapsed27 now = time.time()28 time_passed = now - limiter["last_update"]29 new_tokens = time_passed * (limiter["rate"] / 60.0)30 limiter["tokens"] = min(limiter["rate"], limiter["tokens"] + new_tokens)31 limiter["last_update"] = now3233 if limiter["tokens"] < 1:34 # Calculate wait time until a token is available35 wait_time = (1 - limiter["tokens"]) * (60.0 / limiter["rate"])36 await asyncio.sleep(wait_time)37 limiter["tokens"] = 138 limiter["last_update"] = time.time()3940 # Consume a token41 limiter["tokens"] -= 14243 def _extract_domain(self, url):44 """Extract the domain from a URL."""45 parsed = urllib.parse.urlparse(url)46 return parsed.netloc
Advanced technique: These optimizations show how to balance speed and resource usage. Caching prevents redundant research, parallel processing maximizes throughput, and rate limiting ensures respectful use of external services.
10. Security and Ethical Considerations
Securing Your Knowledge Companion
Implement these security measures to protect your system and users:
- Input validation: Sanitize all user inputs to prevent injection attacks
- Rate limiting: Prevent abuse by limiting requests per user
- Authentication: Require user authentication for sensitive operations
- Secure storage: Encrypt sensitive data and API keys
- Audit logging: Track all system actions for review
Example implementation of authentication middleware:
python1from fastapi import Depends, HTTPException, status2from fastapi.security import OAuth2PasswordBearer3import jwt4from datetime import datetime, timedelta56# Setup7oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")8SECRET_KEY = "your-secret-key" # Store securely in environment variables9ALGORITHM = "HS256"1011# User authentication12async def get_current_user(token: str = Depends(oauth2_scheme)):13 credentials_exception = HTTPException(14 status_code=status.HTTP_401_UNAUTHORIZED,15 detail="Invalid authentication credentials",16 headers={"WWW-Authenticate": "Bearer"},17 )1819 try:20 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])21 username: str = payload.get("sub")22 if username is None:23 raise credentials_exception24 except jwt.PyJWTError:25 raise credentials_exception2627 # Get user from database (simplified)28 user = get_user(username)29 if user is None:30 raise credentials_exception3132 return user3334# Example protected endpoint35@app.post("/research/start", response_model=ResearchStatus)36async def start_research(37 query: Query,38 background_tasks: BackgroundTasks,39 current_user = Depends(get_current_user)40):41 # Check user permissions42 if not user_can_research(current_user):43 raise HTTPException(44 status_code=status.HTTP_403_FORBIDDEN,45 detail="Not enough permissions"46 )4748 # Continue with research...
Ethical Web Scraping
Follow these guidelines for responsible web navigation:
- Respect robots.txt: Check for permission before crawling
- Identify your bot: Set proper User-Agent strings
- Rate limiting: Don't overload websites with requests
- Cache results: Minimize duplicate requests
- Honor copyright: Respect terms of service and licensing
Implementation example:
python1class EthicalBrowser(IntelligentBrowser):2 def __init__(self, headless=True, user_agent=None):3 """Initialize with ethical browsing capabilities."""4 super().__init__(headless)56 # Set an honest user agent7 if user_agent is None:8 user_agent = "KnowledgeCompanionBot/1.0 (+https://yourwebsite.com/bot.html)"9 self.browser.set_user_agent(user_agent)1011 # Initialize robots.txt cache12 self.robots_cache = {}13 self.rate_limiter = RateLimiter()1415 async def visit_page(self, url):16 """Ethically visit a page with proper checks."""17 # Check robots.txt first18 domain = self._extract_domain(url)19 if not await self._can_access(url):20 return {21 "url": url,22 "error": "Access disallowed by robots.txt",23 "content": None24 }2526 # Apply rate limiting27 await self.rate_limiter.acquire(url)2829 # Now perform the visit30 return await super().visit_page(url)3132 async def _can_access(self, url):33 """Check if a URL can be accessed according to robots.txt."""34 domain = self._extract_domain(url)3536 # Check cache first37 if domain in self.robots_cache:38 parser = self.robots_cache[domain]["parser"]39 last_checked = self.robots_cache[domain]["time"]4041 # Refresh cache if older than 1 day42 if time.time() - last_checked > 86400:43 parser = await self._fetch_robots_txt(domain)4445 else:46 # Fetch and parse robots.txt47 parser = await self._fetch_robots_txt(domain)4849 # Check if our user agent can access the URL50 user_agent = self.browser.get_user_agent()51 path = urllib.parse.urlparse(url).path52 return parser.can_fetch(user_agent, path)5354 async def _fetch_robots_txt(self, domain):55 """Fetch and parse robots.txt for a domain."""56 robots_url = f"https://{domain}/robots.txt"5758 # Use a simple GET request, not the browser59 async with aiohttp.ClientSession() as session:60 try:61 async with session.get(robots_url) as response:62 if response.status == 200:63 content = await response.text()64 parser = robotparser.RobotFileParser()65 parser.parse(content.splitlines())66 else:67 # No robots.txt or can't access it - create a permissive parser68 parser = robotparser.RobotFileParser()69 parser.allow_all = True70 except:71 # Error accessing robots.txt - create a permissive parser72 parser = robotparser.RobotFileParser()73 parser.allow_all = True7475 # Cache the result76 self.robots_cache[domain] = {77 "parser": parser,78 "time": time.time()79 }8081 return parser8283 def _extract_domain(self, url):84 """Extract domain from URL."""85 parsed = urllib.parse.urlparse(url)86 return parsed.netloc
Ethical principle: This implementation demonstrates the technical aspects of ethical web scraping by checking robots.txt files, using honest user agents, and implementing rate limiting to be a good citizen of the web.
11. Testing and Quality Assurance
Unit Testing Core Components
Example of testing the Browser-Use module:
python1import unittest2from unittest.mock import MagicMock, patch3import asyncio45class TestIntelligentBrowser(unittest.TestCase):6 @patch('browser_use.BrowserSession')7 def setUp(self, MockBrowserSession):8 self.mock_browser = MockBrowserSession.return_value9 self.intelligent_browser = IntelligentBrowser(headless=True)1011 def test_search(self):12 # Set up mocks13 self.mock_browser.find_element.return_value = "search_box"14 self.intelligent_browser.get_search_results = MagicMock(return_value=[15 {"title": "Test Result", "url": "https://example.com", "snippet": "Example snippet"}16 ])1718 # Execute search19 results = self.intelligent_browser.search("test query")2021 # Verify22 self.mock_browser.navigate.assert_called_with("https://www.google.com")23 self.mock_browser.input_text.assert_called_with("search_box", "test query")24 self.mock_browser.press_enter.assert_called_once()25 self.assertEqual(len(results), 1)26 self.assertEqual(results[0]["title"], "Test Result")2728 def test_extract_main_content(self):29 # Set up mocks for different scenarios30 self.mock_browser.find_element.side_effect = [31 None, # No article32 None, # No main33 "content_div" # Found .content34 ]35 self.mock_browser.get_text.return_value = "Extracted content"3637 # Execute38 content = self.intelligent_browser.extract_main_content()3940 # Verify41 self.assertEqual(content, "Extracted content")42 self.assertEqual(self.mock_browser.find_element.call_count, 3)4344class TestKnowledgeManager(unittest.TestCase):45 def setUp(self):46 self.mock_mcp = MagicMock()47 self.knowledge_manager = KnowledgeSourceManager(self.mock_mcp)4849 def test_query_source(self):50 # Set up51 self.knowledge_manager.available_sources = {52 "test_source": {"description": "Test", "capabilities": [], "parameters": {}}53 }54 self.mock_mcp.query_context.return_value = {"result": "test_data"}5556 # Execute57 result = self.knowledge_manager.query_source("test_source", {"param": "value"})5859 # Verify60 self.mock_mcp.query_context.assert_called_with(61 context_name="test_source",62 parameters={"param": "value"}63 )64 self.assertEqual(result, {"result": "test_data"})6566 def test_query_unknown_source(self):67 # Verify exception for unknown source68 with self.assertRaises(ValueError):69 self.knowledge_manager.query_source("unknown_source", {})
Integration Testing
Test how components work together:
python1class TestOrchestration(unittest.IsolatedAsyncioTestCase):2 async def asyncSetUp(self):3 # Create mocks for all components4 self.mock_llm = MagicMock()5 self.mock_browser = MagicMock()6 self.mock_knowledge_manager = MagicMock()78 # Setup orchestrator with mocks9 self.orchestrator = KnowledgeOrchestrator(10 self.mock_llm,11 self.mock_browser,12 self.mock_knowledge_manager13 )1415 # Setup common test data16 self.mock_llm.complete.return_value = json.dumps({17 "priority_order": ["web_search", "arxiv"],18 "search_terms": {19 "web_search": ["test query"],20 "arxiv": "test query physics"21 },22 "requires_followup": False23 })2425 async def test_full_query_processing(self):26 # Mock the research methods27 self.orchestrator._perform_web_research = MagicMock(28 return_value=asyncio.Future()29 )30 self.orchestrator._perform_web_research.return_value.set_result([31 {"title": "Web Result", "url": "https://example.com"}32 ])3334 self.mock_knowledge_manager.search_arxiv.return_value = {35 "papers": [{"title": "ArXiv Paper", "abstract": "Test abstract"}]36 }3738 # Mock report synthesis39 final_report = "# Research Report\n\nThis is a test report."40 self.mock_llm.complete.side_effect = [41 # First call - query analysis42 json.dumps({43 "priority_order": ["web_search", "arxiv"],44 "search_terms": {45 "web_search": ["test query"],46 "arxiv": "test query physics"47 },48 "requires_followup": False49 }),50 # Second call - report synthesis51 final_report52 ]5354 # Execute full query processing55 result = await self.orchestrator.process_query("test query")5657 # Verify58 self.assertEqual(result, final_report)59 self.assertEqual(self.mock_llm.complete.call_count, 2)60 self.orchestrator._perform_web_research.assert_called_once()61 self.mock_knowledge_manager.search_arxiv.assert_called_once()
End-to-End Testing
Test the entire system with real inputs:
python1class TestEndToEnd(unittest.IsolatedAsyncioTestCase):2 async def asyncSetUp(self):3 # Create real components with test configuration4 self.llm_client = LlamaClient(model="llama3.2-test")5 self.browser = IntelligentBrowser(headless=True)6 self.mcp_client = MCPClient(7 server_url="https://test-mcp.example.com",8 api_key="test_key"9 )10 self.knowledge_manager = KnowledgeSourceManager(self.mcp_client)1112 # Set up the orchestrator with real components13 self.orchestrator = KnowledgeOrchestrator(14 self.llm_client,15 self.browser,16 self.knowledge_manager17 )1819 # Create the CLI interface20 self.cli = KnowledgeCompanionCLI(self.orchestrator)2122 async def asyncTearDown(self):23 # Clean up resources24 self.browser.close()2526 @unittest.skip("End-to-end test requires real services")27 async def test_basic_query(self):28 """Test end-to-end with a simple query."""29 # This test is skipped by default as it requires real services30 query = "What is the capital of France?"31 report = await self.orchestrator.process_query(query)3233 # Verify basic expectations about the report34 self.assertIn("Paris", report)35 self.assertIn("France", report)36 self.assertGreater(len(report), 100) # Ensure substantial content
Testing principle: The test suite demonstrates how to test at multiple levels—unit tests for individual functions, integration tests for component interactions, and end-to-end tests for the complete system. Note the use of mocks to isolate components during testing.
12. Deployment Strategies
Docker Containerization
Create a Dockerfile for your Knowledge Companion:
dockerfile1# Use a Python base image2FROM python:3.9-slim34# Install system dependencies including Chrome/Chromium5RUN apt-get update && apt-get install -y \6 wget \7 gnupg \8 unzip \9 && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \10 && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \11 && apt-get update \12 && apt-get install -y google-chrome-stable \13 && rm -rf /var/lib/apt/lists/*1415# Install ChromeDriver16RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \17 wget -N chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \18 unzip ~/chromedriver_linux64.zip -d ~/ && \19 rm ~/chromedriver_linux64.zip && \20 mv -f ~/chromedriver /usr/local/bin/chromedriver && \21 chmod +x /usr/local/bin/chromedriver2223# Set up workdir and install Python dependencies24WORKDIR /app25COPY requirements.txt .26RUN pip install --no-cache-dir -r requirements.txt2728# Download and configure Ollama (for local LLM support)29RUN wget -O ollama https://ollama.ai/download/ollama-linux-amd64 && \30 chmod +x ollama && \31 mv ollama /usr/local/bin/3233# Copy application code34COPY . .3536# Create a non-root user37RUN useradd -m appuser38USER appuser3940# Set environment variables41ENV PYTHONUNBUFFERED=142ENV BROWSER_USE_HEADLESS=true43ENV OLLAMA_HOST=host.docker.internal4445# Expose port for the API46EXPOSE 80004748# Health check49HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \50 CMD curl -f http://localhost:8000/health || exit 15152# Command to run the application53CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Using Docker Compose for Multi-Container Deployment
Create a docker-compose.yml file:
yaml1version: '3.8'23services:4 # API Service5 api:6 build: .7 ports:8 - "8000:8000"9 environment:10 - REDIS_URL=redis://redis:637911 - MCP_SERVER_URL=http://mcp:500012 depends_on:13 - redis14 - mcp15 - ollama16 volumes:17 - ./app:/app18 networks:19 - knowledge-network2021 # Redis for caching22 redis:23 image: redis:6.2-alpine24 ports:25 - "6379:6379"26 volumes:27 - redis-data:/data28 command: redis-server --appendonly yes29 networks:30 - knowledge-network3132 # MCP Server33 mcp:34 build: ./mcp-server35 ports:36 - "5000:5000"37 environment:38 - PYTHONUNBUFFERED=139 volumes:40 - ./mcp-server:/app41 networks:42 - knowledge-network4344 # Ollama for local LLM support45 ollama:46 image: ollama/ollama:latest47 volumes:48 - ollama-data:/root/.ollama49 ports:50 - "11434:11434"51 networks:52 - knowledge-network5354networks:55 knowledge-network:56 driver: bridge5758volumes:59 redis-data:60 ollama-data:
Serverless Deployment
For AWS Lambda deployment, create a serverless.yml configuration:
yaml1service: knowledge-companion23provider:4 name: aws5 runtime: python3.96 region: us-east-17 memorySize: 20488 timeout: 309 environment:10 MCP_SERVER_URL: ${env:MCP_SERVER_URL}11 REDIS_URL: ${env:REDIS_URL}1213functions:14 api:15 handler: serverless_handler.handler16 events:17 - http:18 path: /19 method: ANY20 - http:21 path: /{proxy+}22 method: ANY23 layers:24 - !Ref PythonDependenciesLambdaLayer2526layers:27 pythonDependencies:28 path: layer29 compatibleRuntimes:30 - python3.93132custom:33 pythonRequirements:34 dockerizePip: true35 slim: true36 layer: true3738plugins:39 - serverless-python-requirements40 - serverless-offline
Deployment concept: These examples show multiple deployment strategies—containerized for easy distribution, Docker Compose for multi-service orchestration, and serverless for scalable API deployment. The actual choice depends on your specific requirements and infrastructure constraints.
13. Practical Applications and Use Cases
Academic Research Assistant
Customize your Knowledge Companion for academic research:
python1class AcademicResearchOrchestrator(KnowledgeOrchestrator):2 """Specialized orchestrator for academic research."""34 async def _analyze_query(self, query):5 """Academic-focused query analysis."""6 prompt = f"""7 Analyze the following academic research query and determine the best approach:89 QUERY: {query}1011 Please determine:12 1. Which academic fields are most relevant to this query?13 2. What specific academic databases should be consulted? (arXiv, PubMed, etc.)14 3. What are the key search terms for academic literature?15 4. Are there specific authors or institutions known for work in this area?16 5. What time period is most relevant for this research?1718 Return your analysis as a structured JSON object.19 """2021 response = await self.llm.complete(prompt)22 return json.loads(response)2324 async def _perform_literature_review(self, analysis):25 """Conduct a comprehensive literature review."""26 sources = []2728 # Search across multiple academic databases29 if "arxiv" in analysis.get("databases", []):30 arxiv_results = await self._search_arxiv(31 analysis["search_terms"],32 analysis.get("categories", [])33 )34 sources.append(("arxiv", arxiv_results))3536 if "pubmed" in analysis.get("databases", []):37 pubmed_results = await self._search_pubmed(38 analysis["search_terms"],39 analysis.get("date_range", {})40 )41 sources.append(("pubmed", pubmed_results))4243 # Find and analyze citation networks44 key_papers = self._identify_key_papers(sources)45 citation_network = await self._analyze_citations(key_papers)4647 return {48 "primary_sources": sources,49 "key_papers": key_papers,50 "citation_network": citation_network51 }5253 async def _generate_academic_report(self, query, research_results):54 """Generate an academic-style report with proper citations."""55 prompt = f"""56 Create a comprehensive academic literature review based on the following research:5758 QUERY: {query}5960 RESEARCH FINDINGS: {json.dumps(research_results, indent=2)}6162 Your review should:63 1. Begin with an abstract summarizing the findings64 2. Include a methodology section explaining the search strategy65 3. Present findings organized by themes or chronologically66 4. Discuss conflicts and agreements in the literature67 5. Identify research gaps68 6. Include a properly formatted bibliography (APA style)6970 Format the review in Markdown.71 """7273 review = await self.llm.complete(prompt)74 return review
Business Intelligence Tool
Adapt your Knowledge Companion for business intelligence:
python1class BusinessIntelligenceOrchestrator(KnowledgeOrchestrator):2 """Specialized orchestrator for business intelligence."""34 async def _analyze_query(self, query):5 """Business-focused query analysis."""6 prompt = f"""7 Analyze the following business intelligence query and determine the best approach:89 QUERY: {query}1011 Please determine:12 1. What industry sectors are relevant to this query?13 2. What specific companies should be researched?14 3. What types of business data would be most valuable (financial, strategic, competitive)?15 4. What time frame is relevant for this analysis?16 5. What business news sources would be most appropriate?1718 Return your analysis as a structured JSON object.19 """2021 response = await self.llm.complete(prompt)22 return json.loads(response)2324 async def _gather_company_data(self, companies):25 """Gather data about specific companies."""26 results = []2728 for company in companies:29 # Financial data30 financial_data = await self._fetch_financial_data(company)3132 # News and press releases33 news = await self._fetch_company_news(company)3435 # Social media sentiment36 sentiment = await self._analyze_social_sentiment(company)3738 results.append({39 "company": company,40 "financial": financial_data,41 "news": news,42 "sentiment": sentiment43 })4445 return results4647 async def _perform_competitive_analysis(self, primary_company, competitors):48 """Analyze competitive positioning."""49 company_data = await self._gather_company_data([primary_company] + competitors)5051 # Extract the primary company data52 primary_data = next(item for item in company_data if item["company"] == primary_company)5354 # Extract competitor data55 competitor_data = [item for item in company_data if item["company"] != primary_company]5657 # Perform SWOT analysis58 swot = await self._generate_swot_analysis(primary_data, competitor_data)5960 return {61 "primary_company": primary_data,62 "competitors": competitor_data,63 "swot_analysis": swot64 }6566 async def _generate_business_report(self, query, intelligence_data):67 """Generate a business intelligence report."""68 prompt = f"""69 Create a comprehensive business intelligence report based on the following data:7071 QUERY: {query}7273 INTELLIGENCE DATA: {json.dumps(intelligence_data, indent=2)}7475 Your report should:76 1. Begin with an executive summary77 2. Include market overview and trends78 3. Provide detailed company analyses79 4. Present competitive comparisons with data visualizations (described in text)80 5. Offer strategic recommendations81 6. Include reference sources8283 Format the report in Markdown with sections for easy navigation.84 """8586 report = await self.llm.complete(prompt)87 return report
Personal Learning Assistant
Create a personal learning companion:
python1class LearningPathOrchestrator(KnowledgeOrchestrator):2 """Specialized orchestrator for creating personalized learning paths."""34 async def create_learning_path(self, subject, user_level="beginner", goals=None):5 """Create a personalized learning path for a subject."""6 analysis = await self._analyze_learning_needs(subject, user_level, goals)7 resources = await self._discover_learning_resources(analysis)8 curriculum = await self._design_curriculum(analysis, resources)910 return {11 "subject": subject,12 "user_level": user_level,13 "goals": goals,14 "curriculum": curriculum15 }1617 async def _analyze_learning_needs(self, subject, user_level, goals):18 """Analyze the learning requirements for a subject."""19 prompt = f"""20 Analyze the following learning request and determine the optimal approach:2122 SUBJECT: {subject}23 USER LEVEL: {user_level}24 LEARNING GOALS: {goals if goals else 'General proficiency'}2526 Please determine:27 1. What are the foundational concepts needed for this subject?28 2. What is an appropriate learning sequence?29 3. What types of resources would be most beneficial (textbooks, videos, interactive exercises)?30 4. How should progress be measured?31 5. What are common stumbling blocks for learners at this level?3233 Return your analysis as a structured JSON object.34 """3536 response = await self.llm.complete(prompt)37 return json.loads(response)3839 async def _discover_learning_resources(self, analysis):40 """Find optimal learning resources based on analysis."""41 resources = {42 "textbooks": [],43 "online_courses": [],44 "videos": [],45 "practice_resources": [],46 "communities": []47 }4849 # Search for textbooks and books50 book_results = await self._search_books(analysis["subject"], analysis["key_concepts"])51 resources["textbooks"] = book_results[:5] # Top 5 relevant books5253 # Search for online courses54 course_results = await self._search_online_courses(55 analysis["subject"],56 analysis["user_level"]57 )58 resources["online_courses"] = course_results[:5]5960 # Find educational videos61 video_results = await self._search_educational_videos(62 analysis["subject"],63 analysis["key_concepts"]64 )65 resources["videos"] = video_results[:8]6667 # Find practice resources68 practice_results = await self._search_practice_resources(analysis["subject"])69 resources["practice_resources"] = practice_results[:5]7071 # Find learning communities72 community_results = await self._search_learning_communities(analysis["subject"])73 resources["communities"] = community_results[:3]7475 return resources7677 async def _design_curriculum(self, analysis, resources):78 """Design a structured curriculum based on analysis and resources."""79 prompt = f"""80 Create a comprehensive learning curriculum based on the following:8182 SUBJECT ANALYSIS: {json.dumps(analysis, indent=2)}8384 AVAILABLE RESOURCES: {json.dumps(resources, indent=2)}8586 Your curriculum should:87 1. Be organized in modules with clear learning objectives for each88 2. Include a mix of theory and practice89 3. Estimate time commitments for each module90 4. Recommend specific resources for each module91 5. Include checkpoints to assess understanding92 6. Provide a progression from foundational to advanced concepts9394 Format the curriculum in Markdown with clear sections.95 """9697 curriculum = await self.llm.complete(prompt)98 return curriculum
Application principle: These specialized orchestrators show how the same core technology can be adapted to different domains by modifying the analysis, research strategies, and report generation based on domain-specific requirements.
14. Conclusion: The Future of AI Knowledge Systems
The AI Knowledge Companion we've built represents just the beginning of a new era in information processing. As we look to the future, several trends are emerging:
-
Deeper reasoning capabilities: Future systems will go beyond information retrieval to perform logical reasoning, connecting disparate pieces of information to generate novel insights.
-
Multimodal understanding: The next generation of knowledge systems will process not just text but images, audio, and video seamlessly, extracting information from all media types.
-
Collective intelligence: Knowledge companions will facilitate collaboration between multiple human experts and AI systems, creating hybrid intelligence networks.
-
Continuous learning: Systems will remember past interactions and continuously refine their understanding based on user feedback and new information.
-
Specialized domain expertise: We'll see the rise of highly specialized companions focused on specific fields like medicine, law, or engineering.
The technologies we've explored—Browser-Use and MCP—are foundational pieces of this future, enabling AI systems to navigate the internet and connect to diverse knowledge sources in standardized ways. As these technologies mature, the boundary between AI assistants and knowledge workers will continue to blur, creating powerful partnerships that enhance human capabilities.
By building your own AI Knowledge Companion, you're not just creating a research tool—you're participating in the development of systems that will fundamentally transform how humans interact with the vast landscape of global knowledge.
Additional Resources
- Browser-Use Documentation
- Model Context Protocol Specification
- Ollama GitHub Repository
- Ethical Web Scraping Guidelines
- FastAPI Documentation
- Redis Documentation
- Docker and Docker Compose Documentation
- ArXiv API Documentation
Happy knowledge exploration!

Sovereign AI: Building Local-First Intelligent Systems
by Daniel Kliewer · Paperback · 72 pages
The hands-on guide to building AI that runs on your hardware, keeps your data private, and eliminates cloud dependence. Working code included.