# Directory Structure
```
├── .cursor
│   ├── mcp.json
│   └── python.mdc
├── .env.example
├── .gitignore
├── browser.py
├── llms-install.md
├── README.md
├── requirements.txt
└── server.py
```
# Files
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | 
2 | ANTHROPIC_API_KEY=api-key-here
3 | LOG_LEVEL=CRITICAL
4 | BROWSER_USE_LOGGING_LEVEL=CRITICAL
5 | LANGCHAIN_TRACING_V2=false
6 | LANGCHAIN_VERBOSE=false
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
 1 | # Environment variables
 2 | .env
 3 | 
 4 | # Virtual Environment
 5 | .venv/
 6 | venv/
 7 | env/
 8 | ENV/
 9 | 
10 | # Python cache files
11 | __pycache__/
12 | *.py[cod]
13 | *$py.class
14 | *.so
15 | .Python
16 | 
17 | # Distribution / packaging
18 | dist/
19 | build/
20 | *.egg-info/
21 | *.egg
22 | 
23 | # Installer logs
24 | pip-log.txt
25 | pip-delete-this-directory.txt
26 | 
27 | # Unit test / coverage reports
28 | htmlcov/
29 | .tox/
30 | .coverage
31 | .coverage.*
32 | .cache
33 | nosetests.xml
34 | coverage.xml
35 | *.cover
36 | .hypothesis/
37 | 
38 | # Jupyter Notebook
39 | .ipynb_checkpoints
40 | 
41 | # IDE specific files
42 | .idea/
43 | .vscode/
44 | *.swp
45 | *.swo
46 | .DS_Store
47 | 
48 | # Browser-use specific
49 | *.gif
50 | browser_screenshots/ 
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
 1 | # Uber Eats MCP Server
 2 | 
 3 | This is a POC of how you can build an MCP servers on top of Uber Eats
 4 | 
 5 | https://github.com/user-attachments/assets/05efbf51-1b95-4bd2-a327-55f1fe2f958b
 6 | 
 7 | ## What is MCP?
 8 | 
 9 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open protocol that enables seamless integration between LLM applications and external tools.
10 | 
11 | ## Prerequisites
12 | 
13 | - Python 3.12 or higher
14 | - Anthropic API key or other supported LLM provider
15 | 
16 | ## Setup
17 | 
18 | 1. Ensure you have a virtual environment activated:
19 |    ```
20 |    uv venv
21 |    source .venv/bin/activate  # On Unix/Mac
22 |    ```
23 | 
24 | 2. Install required packages:
25 |    ```
26 |    uv pip install -r requirements.txt
27 |    playwright install
28 |    ```
29 | 
30 | 3. Update the `.env` file with your API key:
31 |    ```
32 |    ANTHROPIC_API_KEY=your_openai_api_key_here
33 |    ```
34 | 
35 | ## Note
36 | 
37 | Since we're using stdio as MCP transport, we have disable all output from browser use
38 | 
39 | ## Debugging
40 | 
41 | You can run the MCP inspector tool with this command
42 | 
43 | ```bash
44 | uv run mcp dev server.py
45 | ```
46 | 
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | browser-use
2 | langchain-anthropic
3 | langchain-openai
4 | python-dotenv
5 | fastmcp 
6 | mcp[cli]
```
--------------------------------------------------------------------------------
/.cursor/mcp.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "mcpServers": {
 3 |       "uber_eats": {
 4 |           "command": "uv",
 5 |           "args": [
 6 |               "--directory",
 7 |               "/Users/ericzakariasson/dev/uber-eats-mcp-server",
 8 |               "run",
 9 |               "server.py"
10 |           ]
11 |       }
12 |   }
13 | }
```
--------------------------------------------------------------------------------
/browser.py:
--------------------------------------------------------------------------------
```python
 1 | from typing import Awaitable, Callable
 2 | from browser_use import Agent, Browser, BrowserConfig
 3 | from dotenv import load_dotenv
 4 | from langchain_openai import ChatOpenAI
 5 | from langchain_anthropic import ChatAnthropic
 6 | import warnings
 7 | 
 8 | load_dotenv()
 9 | 
10 | warnings.filterwarnings("ignore")
11 | 
12 | browser = Browser(
13 |     config=BrowserConfig(
14 |         chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',  # macOS path
15 |     )
16 | )
17 | 
18 | llm = ChatAnthropic(model_name="claude-3-5-sonnet-latest")
19 | 
20 | task_template = """
21 | perform the following task
22 | {task}
23 | """
24 | 
25 | async def run_browser_agent(task: str, on_step: Callable[[], Awaitable[None]]):
26 |     """Run the browser-use agent with the specified task."""
27 |     agent = Agent(
28 |         task=task_template.format(task=task),
29 |         browser=browser,
30 |         llm=llm,
31 |         register_new_step_callback=on_step,
32 |         register_done_callback=on_step,
33 |     )
34 | 
35 |     result = await agent.run()
36 | 
37 |     await browser.close()
38 |     
39 |     return result.final_result()
40 | 
```
--------------------------------------------------------------------------------
/llms-install.md:
--------------------------------------------------------------------------------
```markdown
 1 | # AI Agents -- Read this!
 2 | 
 3 | This guide provides instructions for AI agents to set up the Uber Eats MCP server on behalf of the user.
 4 | 
 5 | ## Prerequisites
 6 | 
 7 | - Python 3.12 or higher
 8 | - Anthropic API key or other supported LLM provider
 9 | 
10 | ## Setup
11 | 
12 | 1. Create and activate a virtual environment:
13 | 
14 |    ```
15 |    uv venv
16 |    source .venv/bin/activate  # On Unix/Mac
17 |    ```
18 | 
19 | 2. Install required packages:
20 |    ```
21 |    uv pip install -r requirements.txt
22 |    playwright install
23 |    ```
24 | 
25 | ## MCP Configuration
26 | 
27 | To use this server with MCP-compatible applications, you need to add it to your MCP settings file:
28 | 
29 | **Important Notes:**
30 | 
31 | - Replace `/path/to/uber-eats-mcp-server` with the actual path to your installation
32 | - Replace the censored `ANTHROPIC_API_KEY` with your actual Anthropic API key
33 | - All environment variables can be set directly in the MCP settings JSON file, so you don't need to update the .env file separately
34 | - The command uses `/bin/bash` to activate the virtual environment before running the server
35 | - You may need to restart your application after updating the MCP settings
36 | 
37 | ## Available Tools
38 | 
39 | This MCP server provides the following tools:
40 | 
41 | 1. `find_menu_options`: Search Uber Eats for restaurants or food items
42 | 
43 |    - Parameters: `search_term` (string) - Food or restaurant to search for
44 |    - Returns a resource URI that can be used to retrieve the results after a few minutes
45 | 
46 | 2. `order_food`: Order food from a restaurant
47 |    - Parameters:
48 |      - `item_url` (string) - URL of the item to order
49 |      - `item_name` (string) - Name of the item to order
50 | 
51 | ## Example Usage
52 | 
53 | ```python
54 | # Search for pizza options
55 | result = await use_mcp_tool(
56 |     server_name="github.com/ericzakariasson/uber-eats-mcp-server",
57 |     tool_name="find_menu_options",
58 |     arguments={"search_term": "pizza"}
59 | )
60 | 
61 | # Wait for the search to complete (about 2 minutes)
62 | # Then retrieve the results using the resource URI
63 | search_results = await access_mcp_resource(
64 |     server_name="github.com/ericzakariasson/uber-eats-mcp-server",
65 |     uri="resource://search_results/{request_id}"  # request_id from the previous result
66 | )
67 | 
68 | # Order food using the URL from the search results
69 | order_result = await use_mcp_tool(
70 |     server_name="github.com/ericzakariasson/uber-eats-mcp-server",
71 |     tool_name="order_food",
72 |     arguments={
73 |         "item_url": "https://www.ubereats.com/...",  # URL from search results
74 |         "item_name": "Pepperoni Pizza"
75 |     }
76 | )
77 | ```
78 | 
79 | ## Troubleshooting
80 | 
81 | If you encounter connection issues:
82 | 
83 | 1. Make sure the virtual environment is activated in the MCP settings file command
84 | 2. Check that the paths in your MCP settings file are correct
85 | 3. Verify that your Anthropic API key is valid
86 | 4. Try adjusting the log levels in the env section of your MCP settings
87 | 5. Restart your application after making changes to the MCP settings
88 | 
```
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
```python
  1 | #!/usr/bin/env python3
  2 | import asyncio
  3 | from dotenv import load_dotenv
  4 | from mcp.server.fastmcp import FastMCP, Context
  5 | from browser import run_browser_agent
  6 | 
  7 | # Load environment variables from .env file
  8 | load_dotenv()
  9 | 
 10 | # Initialize FastMCP server
 11 | mcp = FastMCP("uber_eats")
 12 | 
 13 | # In-memory storage for search results
 14 | search_results = {}
 15 | 
 16 | @mcp.tool()
 17 | async def find_menu_options(search_term: str, context: Context) -> str:
 18 |     """Search Uber Eats for restaurants or food items.
 19 |     
 20 |     Args:
 21 |         search_term: Food or restaurant to search for
 22 |     """
 23 |     
 24 |     # Create the search task
 25 |     task = f"""
 26 | 0. Start by going to: https://www.ubereats.com/se-en/
 27 | 1. Type "{search_term}" in the global search bar and press enter
 28 | 2. Go to the first search result (this is the most popular restaurant).
 29 | 3. When you can see the menu options for the resturant, we need to use the specific search input for the resturant located under the banned (identify it by the placeholder "Search in [restaurant name]"
 30 | 4. Click the input field and type "{search_term}", then press enter
 31 | 5. Check for menu options related to "{search_term}"
 32 | 6. Get the name, url and price of the top 3 items related to "{search_term}". URL is very important
 33 | """
 34 |     
 35 |     search_results[context.request_id] = f"Search for '{search_term}' in progress. Check back in 30 seconds"
 36 | 
 37 |     asyncio.create_task(
 38 |         perform_search(context.request_id, search_term, task, context)
 39 |     )    
 40 |     
 41 |     return f"Search for '{search_term}' started. Please wait for 2 minutes, then you can retrieve results using the resource URI: resource://search_results/{context.request_id}. Use a terminal sleep statement to wait for 2 minutes."
 42 | 
 43 | async def perform_search(request_id: str, search_term: str, task: str, context: Context):
 44 |     """Perform the actual search in the background."""
 45 |     try:
 46 |         step_count = 0
 47 |         
 48 |         async def step_handler(*args, **kwargs):
 49 |             nonlocal step_count
 50 |             step_count += 1
 51 |             await context.info(f"Step {step_count} completed")
 52 |             await context.report_progress(step_count)
 53 |         
 54 |         result = await run_browser_agent(task=task, on_step=step_handler)
 55 |         
 56 |         search_results[request_id] = result
 57 |     
 58 |     except Exception as e:
 59 |         # Store the error with the request ID
 60 |         search_results[request_id] = f"Error: {str(e)}"
 61 |         await context.error(f"Error searching for '{search_term}': {str(e)}")
 62 | 
 63 | @mcp.resource(uri="resource://search_results/{request_id}")
 64 | async def get_search_results(request_id: str) -> str:
 65 |     """Get the search results for a given request ID.
 66 |     
 67 |     Args:
 68 |         request_id: The ID of the request to get the search results for
 69 |     """
 70 |     # Check if the results exist
 71 |     if request_id not in search_results:
 72 |         return f"No search results found for request ID: {request_id}"
 73 |     
 74 |     # Return the successful search results
 75 |     return search_results[request_id]
 76 | 
 77 | @mcp.tool()
 78 | async def order_food(item_url: str, item_name: str, context: Context) -> str:
 79 |     """Order food from a restaurant.
 80 |     
 81 |     Args:
 82 |         restaurant_url: URL of the restaurant
 83 |         item_name: Name of the item to order
 84 |     """
 85 |     
 86 |     task = f"""
 87 | 1. Go to {item_url}
 88 | 2. Click "Add to order"
 89 | 3. Wait 3 seconds
 90 | 4. Click "Go to checkout"
 91 | 5. If there are upsell modals, click "Skip"
 92 | 6. Click "Place order"
 93 | """
 94 |     
 95 |     # Start the background task for ordering
 96 |     asyncio.create_task(
 97 |         perform_order(item_url, item_name, task, context)
 98 |     )
 99 |     
100 |     # Return a message immediately
101 |     return f"Order for '{item_name}' started. Your order is being processed."
102 | 
103 | async def perform_order(restaurant_url: str, item_name: str, task: str, context: Context):
104 |     """Perform the actual food ordering in the background."""
105 |     try:
106 |         step_count = 0
107 |         
108 |         async def step_handler(*args, **kwargs):
109 |             nonlocal step_count
110 |             step_count += 1
111 |             await context.info(f"Order step {step_count} completed")
112 |             await context.report_progress(step_count)
113 |         
114 |         result = await run_browser_agent(task=task, on_step=step_handler)
115 |         
116 |         # Report completion
117 |         await context.info(f"Order for '{item_name}' has been placed successfully!")
118 |         return result
119 |     
120 |     except Exception as e:
121 |         error_msg = f"Error ordering '{item_name}': {str(e)}"
122 |         await context.error(error_msg)
123 |         return error_msg
124 | 
125 | if __name__ == "__main__":
126 |     mcp.run(transport='stdio') 
127 | 
```