bridge-mcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,521 @@
1
+ Metadata-Version: 2.4
2
+ Name: bridge-mcp
3
+ Version: 1.0.0
4
+ Summary: Universal PC Control MCP - Give any AI full control over Windows
5
+ Project-URL: Homepage, https://github.com/BarhamAgha1/Bridge-MCP
6
+ Author: Barham Agha
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: fastmcp>=2.0.0
10
+ Requires-Dist: fuzzywuzzy>=0.18.0
11
+ Requires-Dist: keyboard>=0.13.5
12
+ Requires-Dist: mouse>=0.7.1
13
+ Requires-Dist: pillow>=10.0.0
14
+ Requires-Dist: psutil>=5.9.0
15
+ Requires-Dist: pyautogui>=0.9.54
16
+ Requires-Dist: pyperclip>=1.8.2
17
+ Requires-Dist: python-levenshtein>=0.21.0
18
+ Requires-Dist: uiautomation>=2.0.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # 🌉 Bridge MCP
22
+
23
+ ### Universal PC Control for Any AI
24
+
25
+ [![FastMCP](https://img.shields.io/badge/FastMCP-2.0-blue?style=for-the-badge&logo=python)](https://fastmcp.cloud)
26
+ [![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE)
27
+ [![Python](https://img.shields.io/badge/Python-3.10+-yellow?style=for-the-badge&logo=python)](https://python.org)
28
+ [![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?style=for-the-badge&logo=windows)](https://www.microsoft.com/windows)
29
+
30
+ **Give any AI complete control over your Windows PC**
31
+
32
+ [Features](#-features) • [Quick Start](#-quick-start) • [Configuration](#-configuration) • [Tools](#-available-tools) • [Troubleshooting](#-troubleshooting) • [Contributing](#-contributing)
33
+
34
+ ---
35
+
36
+ ## 🔄 How Persistence Works
37
+
38
+ Bridge MCP v2.0 stores agent registrations in a **persistent JSON file**:
39
+
40
+ - **Windows:** `%APPDATA%\bridge-mcp\agents.json`
41
+ - **Linux/Mac:** `~/.config/bridge-mcp/agents.json`
42
+
43
+ This means:
44
+ - ✅ Register once, works forever
45
+ - ✅ Survives Claude Code session restarts
46
+ - ✅ Survives computer reboots
47
+ - ✅ Works across all AI clients
48
+
49
+ ### First-Time Setup
50
+
51
+ 1. **Start the local agent:**
52
+ ```bash
53
+ cd Bridge-MCP
54
+ python local_agent.py
55
+ ```
56
+
57
+ 2. **The agent auto-registers itself** - no manual registration needed!
58
+
59
+ 3. **Verify in any Claude session:**
60
+ ```
61
+ Use list_agents() to see registered agents
62
+ ```
63
+
64
+ ### Troubleshooting
65
+
66
+ If you see "No agents connected":
67
+
68
+ 1. **Check if local_agent.py is running** - it must be running in a terminal
69
+ 2. **Check health:** Use `check_agent_health()` tool
70
+ 3. **Manual register:** Use `register_agent("local", "http://127.0.0.1:8006", "My PC")`
71
+
72
+ ### Running Local Agent as Background Service
73
+
74
+ For always-on access, install local_agent as a Windows service:
75
+ ```bash
76
+ python install_service.py install
77
+ python install_service.py start
78
+ ```
79
+
80
+ To remove:
81
+ ```bash
82
+ python install_service.py stop
83
+ python install_service.py remove
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 🎯 What is Bridge MCP?
89
+
90
+ Bridge MCP is a **Model Context Protocol (MCP)** server that gives **any AI** full control over a Windows PC. Whether you're using Claude, ChatGPT, Cursor, Gemini, or any other MCP-compatible AI, Bridge MCP lets you:
91
+
92
+ * 🖥️ **Control Applications** - Launch, switch, resize, close any app
93
+ * 🖱️ **Automate Input** - Mouse clicks, keyboard typing, hotkeys, scrolling
94
+ * 📸 **See the Screen** - Screenshots, UI element detection, desktop state
95
+ * 🌐 **Browse the Web** - Full Chrome automation and control
96
+ * ⚡ **Run Commands** - PowerShell, CMD, file operations
97
+ * 📋 **Manage Clipboard** - Copy, paste, clear
98
+
99
+ > **Think of it as giving your AI eyes and hands to control your computer!**
100
+
101
+ ---
102
+
103
+ ## ✨ Features
104
+
105
+ | Category | Tools | Description |
106
+ | --- | --- | --- |
107
+ | 🚀 **App Control** | 8 tools | Launch, switch, close, resize, minimize, maximize applications |
108
+ | 🖱️ **Mouse & Keyboard** | 10 tools | Click, type, hotkeys, scroll, drag, move cursor |
109
+ | 📸 **Screen Capture** | 7 tools | Screenshots, desktop state, find UI elements |
110
+ | ⚡ **System** | 8 tools | PowerShell, CMD, file read/write, system info |
111
+ | 🌐 **Browser** | 15 tools | Chrome control, tabs, navigation, web scraping |
112
+ | 📋 **Clipboard** | 3 tools | Copy, paste, clear clipboard |
113
+ | 🔧 **Utilities** | 5+ tools | Wait, dialogs, action sequences |
114
+
115
+ **Total: 40+ powerful tools for complete PC automation!**
116
+
117
+ ---
118
+
119
+ ## 🏗️ Architecture
120
+
121
+ Bridge MCP uses a **Relay Architecture** to work across platforms:
122
+ ```
123
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
124
+ │ Any AI │ │ Cloud Relay │ │ Your Windows │
125
+ │ (Claude, etc.) │◄───────►│ (bridge_mcp) │◄───────►│ PC (Agent) │
126
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
127
+ ```
128
+
129
+ * **bridge_mcp.py** - MCP server (runs locally or on FastMCP Cloud)
130
+ * **local_agent.py** - HTTP server on your PC that executes commands (port 8006)
131
+
132
+ ---
133
+
134
+ ## 🔒 Enterprise-Grade Security
135
+
136
+ Bridge MCP 2.0 includes:
137
+ - **Auth Tokens:** Uses secure Bearer tokens to prevent unauthorized access.
138
+ - **Auto-Config:** Tokens are auto-generated and saved to `agents.json`.
139
+
140
+ ## 🌐 Next-Gen Browser Automation
141
+
142
+ Powered by **Playwright**, Bridge MCP can now:
143
+ - **Click & Type:** Interact with any website element.
144
+ - **Semantic Understanding:** Read page content programmatically.
145
+ - **Headless Mode:** Run automations invisible or visible.
146
+
147
+ ## 🧠 Semantic Computer Vision
148
+
149
+ Bridge MCP "sees" your apps:
150
+ - **UI Tree:** It can read the accessibility tree of Windows apps.
151
+ - **Precision:** Knows exactly where buttons are (no more guessing pixels).
152
+
153
+ ## 🛡️ Safety Sentinel (Human-in-the-Loop)
154
+
155
+ Bridge MCP now puts YOU in control:
156
+ - **Command Interception:** Dangerous commands (writing files, running shell scripts) are BLOCKED by default.
157
+ - **Approval Overlay:** Approval requests appear directly in the AI Activity Overlay with three options:
158
+ - ✓ **APPROVE** - Execute this one command
159
+ - ✗ **DENY** - Block this command
160
+ - ✓ **ALWAYS APPROVE** - Disable Safe Mode and approve all future commands
161
+ - **Dashboard Control:** View all pending requests at `http://localhost:8006`
162
+ - **Safe Mode Toggle:** Switch Safe Mode on/off anytime from the web dashboard
163
+ - **Peace of Mind:** You can leave the agent running without fear of it deleting your files.
164
+
165
+ ## 👁️ Terminator Vision (Live Observability)
166
+
167
+ See what the AI sees, in real-time:
168
+ - **Live Stream:** The dashboard features a low-latency 1080p MJPEG stream of your desktop.
169
+ - **Semantic Overlay:** Green bounding boxes highlight every button, link, and window the AI detects.
170
+ - **Debug Instantly:** Visual confirmation that the AI has found the correct "Submit" button.
171
+
172
+ ## 📚 Full MCP Specification Support
173
+
174
+ Bridge MCP now implements the complete Model Context Protocol specification:
175
+
176
+ ### Resources API
177
+ Expose desktop data as addressable resources:
178
+ - `desktop://screenshot/latest` - Current screenshot
179
+ - `desktop://windows` - Open windows list
180
+ - `desktop://logs` - Agent command logs
181
+ - `file:///{path}` - Read any desktop file
182
+ - `desktop://session/context` - Recent session history
183
+
184
+ ### Prompts API
185
+ Pre-built workflow templates for one-click automation:
186
+ - `automate_desktop_task` - Step-by-step task automation
187
+ - `debug_error` - Interactive error debugging
188
+ - `web_automation` - Playwright web workflows
189
+
190
+ ### Session Memory
191
+ Never lose context:
192
+ - Stores last 100 commands across restarts
193
+ - Provides AI with recent session history
194
+ - Enables "continue where I left off" workflows
195
+
196
+ ---
197
+
198
+ ## 🚀 Quick Start
199
+
200
+ ### Step 1: Clone the Repository
201
+ ```bash
202
+ git clone https://github.com/BarhamAgha1/Bridge-MCP.git
203
+ cd Bridge-MCP
204
+ ```
205
+
206
+ ### Step 2: Install Dependencies
207
+ ```bash
208
+ pip install -r requirements-local.txt
209
+ ```
210
+ **Note:** Playwright browsers (~100MB) auto-install on first use - no manual setup needed!
211
+
212
+ ### Step 3: Start the Local Agent
213
+ ```bash
214
+ python local_agent.py
215
+ ```
216
+
217
+ Keep this terminal open! The agent will display:
218
+ ```
219
+ Bridge MCP Local Agent running on http://127.0.0.1:8006
220
+ ```
221
+
222
+ ### Step 4: Configure Your AI Client
223
+
224
+ See [Configuration](#-configuration) below for Claude Desktop, Cursor, or VS Code setup.
225
+
226
+ ### Step 5: Register Your Agent
227
+
228
+ In your AI conversation, register the local agent:
229
+ ```
230
+ Use register_agent with:
231
+ - agent_id: "my-pc"
232
+ - callback_url: "http://127.0.0.1:8006"
233
+ - agent_name: "My Windows PC"
234
+ ```
235
+
236
+ ### Step 6: Start Controlling!
237
+
238
+ Now use any tool like `screenshot()`, `click(100, 200)`, `type_text("Hello")`, `app_launch("notepad")`, etc.
239
+
240
+ ---
241
+
242
+ ## 🔧 Configuration
243
+
244
+ ### Claude Desktop
245
+
246
+ 1. Open the config file at `%APPDATA%\Claude\claude_desktop_config.json`
247
+
248
+ 2. Add Bridge MCP:
249
+ ```json
250
+ {
251
+ "mcpServers": {
252
+ "bridge-mcp": {
253
+ "command": "python",
254
+ "args": ["C:\\Users\\YourName\\Path\\To\\Bridge-MCP\\bridge_mcp.py"]
255
+ }
256
+ }
257
+ }
258
+ ```
259
+
260
+ ⚠️ **Important:** Replace the path with the **actual location** where you cloned the repository!
261
+
262
+ **Example paths:**
263
+ - `C:\\Users\\PC\\Desktop\\Bridge-MCP\\bridge_mcp.py`
264
+ - `D:\\Projects\\Bridge-MCP\\bridge_mcp.py`
265
+
266
+ 3. **Restart Claude Desktop completely** (close and reopen)
267
+
268
+ ### Cursor
269
+
270
+ Add to your MCP settings in Cursor preferences with the same configuration format.
271
+
272
+ ### VS Code + Claude Code
273
+
274
+ Create `.vscode/mcp.json` in your project:
275
+ ```json
276
+ {
277
+ "mcpServers": {
278
+ "bridge-mcp": {
279
+ "command": "python",
280
+ "args": ["C:\\Users\\YourName\\Path\\To\\Bridge-MCP\\bridge_mcp.py"]
281
+ }
282
+ }
283
+ }
284
+ ```
285
+
286
+ ### Remote Access (Optional)
287
+
288
+ To control your PC from anywhere, expose the local agent with ngrok:
289
+ ```bash
290
+ ngrok http 8006
291
+ ```
292
+
293
+ Then use the ngrok URL (e.g., `https://xxxx.ngrok.io`) as your callback_url when registering.
294
+
295
+ ---
296
+
297
+ ## 🛠️ Available Tools
298
+
299
+ <details>
300
+ <summary><b>🚀 App Control Tools</b></summary>
301
+
302
+ | Tool | Description | Example |
303
+ | --- | --- | --- |
304
+ | `app_launch` | Launch an application | `app_launch("notepad")` |
305
+ | `app_switch` | Switch to open app | `app_switch("Chrome")` |
306
+ | `app_close` | Close an application | `app_close("notepad")` |
307
+ | `app_list` | List all open apps | `app_list()` |
308
+
309
+ </details>
310
+
311
+ <details>
312
+ <summary><b>🖱️ Input Tools (Mouse & Keyboard)</b></summary>
313
+
314
+ | Tool | Description | Example |
315
+ | --- | --- | --- |
316
+ | `click` | Click at coordinates | `click(500, 300)` |
317
+ | `double_click` | Double-click | `double_click(500, 300)` |
318
+ | `right_click` | Right-click | `right_click(500, 300)` |
319
+ | `type_text` | Type text | `type_text("Hello World!")` |
320
+ | `press_key` | Press a key | `press_key("enter")` |
321
+ | `hotkey` | Keyboard shortcut | `hotkey("ctrl,c")` |
322
+ | `scroll` | Scroll | `scroll("down", 3)` |
323
+ | `drag` | Drag and drop | `drag(100, 100, 500, 500)` |
324
+ | `move_mouse` | Move cursor | `move_mouse(500, 300)` |
325
+
326
+ </details>
327
+
328
+ <details>
329
+ <summary><b>📸 Screen Tools</b></summary>
330
+
331
+ | Tool | Description | Example |
332
+ | --- | --- | --- |
333
+ | `screenshot` | Take screenshot | `screenshot()` |
334
+ | `get_desktop_state` | Get full desktop state | `get_desktop_state()` |
335
+ | `get_screen_size` | Get screen dimensions | `get_screen_size()` |
336
+ | `get_mouse_position` | Get cursor position | `get_mouse_position()` |
337
+
338
+ </details>
339
+
340
+ <details>
341
+ <summary><b>⚡ System Tools</b></summary>
342
+
343
+ | Tool | Description | Example |
344
+ | --- | --- | --- |
345
+ | `run_powershell` | Run PowerShell | `run_powershell("Get-Process")` |
346
+ | `run_cmd` | Run CMD command | `run_cmd("dir")` |
347
+ | `file_read` | Read file | `file_read("C:/test.txt")` |
348
+ | `file_write` | Write file | `file_write("C:/test.txt", "Hello")` |
349
+ | `file_list` | List directory | `file_list("C:/Users")` |
350
+
351
+ </details>
352
+
353
+ <details>
354
+ <summary><b>🌐 Browser Tools (Chrome)</b></summary>
355
+
356
+ | Tool | Description | Example |
357
+ | --- | --- | --- |
358
+ | `chrome_open` | Open Chrome | `chrome_open("https://google.com")` |
359
+ | `chrome_navigate` | Go to URL | `chrome_navigate("https://example.com")` |
360
+
361
+ </details>
362
+
363
+ <details>
364
+ <summary><b>🌐 Browser Tools (Playwright - Advanced)</b></summary>
365
+
366
+ | Tool | Description | Example |
367
+ | --- | --- | --- |
368
+ | `browser_navigate` | Go to URL | `browser_navigate("google.com")` |
369
+ | `browser_click` | Click element (CSS) | `browser_click("#submit-btn")` |
370
+ | `browser_type` | Type in element | `browser_type("#search", "hello")` |
371
+ | `browser_press` | Press key | `browser_press("Enter")` |
372
+ | `browser_content` | Get page text | `browser_content()` |
373
+ | `browser_screenshot`| Browser screenshot | `browser_screenshot()` |
374
+
375
+ </details>
376
+
377
+ <details>
378
+ <summary><b>📋 Clipboard Tools</b></summary>
379
+
380
+ | Tool | Description | Example |
381
+ | --- | --- | --- |
382
+ | `clipboard_copy` | Copy to clipboard | `clipboard_copy("Hello")` |
383
+ | `clipboard_paste` | Get clipboard | `clipboard_paste()` |
384
+
385
+ </details>
386
+
387
+ ---
388
+
389
+ ## 💡 Usage Examples
390
+
391
+ ### Example 1: Open Notepad and Write Text
392
+ ```
393
+ User: Open notepad and write "Hello from AI!"
394
+
395
+ AI uses:
396
+ 1. app_launch("notepad")
397
+ 2. wait(1)
398
+ 3. type_text("Hello from AI!")
399
+ ```
400
+
401
+ ### Example 2: Take a Screenshot
402
+ ```
403
+ User: What's on my screen right now?
404
+
405
+ AI uses:
406
+ 1. screenshot()
407
+ 2. [AI analyzes the image and describes what it sees]
408
+ ```
409
+
410
+ ### Example 3: Search on Google
411
+ ```
412
+ User: Search for "Bridge MCP" on Google
413
+
414
+ AI uses:
415
+ 1. chrome_open("https://google.com")
416
+ 2. type_text("Bridge MCP")
417
+ 3. press_key("enter")
418
+ ```
419
+
420
+ ---
421
+
422
+ ## 🔧 Troubleshooting
423
+
424
+ ### Claude Desktop shows "Server disconnected"
425
+
426
+ 1. **Check the path** - Make sure the path in your config points to the actual `bridge_mcp.py` file. The path must be absolute and use double backslashes (`\\`) in JSON.
427
+
428
+ 2. **Test manually** - Open Command Prompt and run:
429
+ ```cmd
430
+ cd "C:\path\to\Bridge-MCP"
431
+ python bridge_mcp.py
432
+ ```
433
+ It should stay running (not exit immediately). Press Ctrl+C to stop.
434
+
435
+ 3. **Install dependencies**:
436
+ ```cmd
437
+ pip install fastmcp httpx
438
+ ```
439
+
440
+ 4. **Restart Claude Desktop** - Fully close and reopen after any config changes.
441
+
442
+ ### Local agent not receiving commands
443
+
444
+ 1. Make sure `local_agent.py` is running in a terminal (keep it open!)
445
+ 2. Verify the callback URL is correct when registering the agent
446
+ 3. For local use: `http://127.0.0.1:8006`
447
+ 4. For remote access: Use ngrok (`ngrok http 8006`) and use the ngrok URL
448
+
449
+ ### "No agents connected" error
450
+
451
+ You need to register your local agent first:
452
+ ```
453
+ register_agent("my-pc", "http://127.0.0.1:8006", "My PC")
454
+ ```
455
+
456
+ ### Unicode/Emoji errors on Windows
457
+
458
+ If `local_agent.py` crashes with Unicode errors, the terminal may not support emojis. This has been fixed in the latest version.
459
+
460
+ ---
461
+
462
+ ## ☁️ FastMCP Cloud Deployment
463
+
464
+ Bridge MCP can be deployed on [FastMCP Cloud](https://fastmcp.cloud) for easy access:
465
+
466
+ 1. Fork this repository
467
+ 2. Go to [fastmcp.cloud](https://fastmcp.cloud)
468
+ 3. Sign in with GitHub
469
+ 4. Create project from your forked repo
470
+ 5. Set entrypoint: `bridge_mcp.py`
471
+ 6. Deploy!
472
+
473
+ Your MCP will be available at: `https://your-project.fastmcp.app/mcp`
474
+
475
+ ---
476
+
477
+ ## 🤝 Contributing
478
+
479
+ Contributions are welcome! Here's how you can help:
480
+
481
+ 1. **Fork** the repository
482
+ 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
483
+ 3. **Commit** your changes (`git commit -m 'Add amazing feature'`)
484
+ 4. **Push** to the branch (`git push origin feature/amazing-feature`)
485
+ 5. **Open** a Pull Request
486
+
487
+ ### Ideas for Contributions
488
+
489
+ * Add more browser support (Firefox, Edge)
490
+ * Add Linux support
491
+ * Add macOS support
492
+ * Add more automation tools
493
+ * Improve UI element detection
494
+ * Add OCR capabilities
495
+
496
+ ---
497
+
498
+ ## 📜 License
499
+
500
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
501
+
502
+ ---
503
+
504
+ ## 🙏 Acknowledgments
505
+
506
+ * [FastMCP](https://fastmcp.cloud) - The amazing MCP framework
507
+ * [Anthropic](https://anthropic.com) - For creating the MCP protocol
508
+
509
+ ---
510
+
511
+ ## 👤 Author
512
+
513
+ **Barham Agha**
514
+
515
+ * GitHub: [@BarhamAgha1](https://github.com/BarhamAgha1)
516
+
517
+ ---
518
+
519
+ **⭐ If you find this project useful, please give it a star! ⭐**
520
+
521
+ Made with ❤️ for the AI community
@@ -0,0 +1,5 @@
1
+ bridge_mcp.py,sha256=pin8PtYpxpzrXZv3HviBm6w6LzGs3optHLGE2-7jSR8,21048
2
+ bridge_mcp-1.0.0.dist-info/METADATA,sha256=HhiUcOxPQfps7BafEqcEw_B81s1GFbA4ZEaARj2teZI,15675
3
+ bridge_mcp-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
4
+ bridge_mcp-1.0.0.dist-info/licenses/LICENSE,sha256=_MU_SJopMfznjUYqn3fqBbNAdah3X0H81b4Hk1LSybA,1089
5
+ bridge_mcp-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Barham Agha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
bridge_mcp.py ADDED
@@ -0,0 +1,595 @@
1
+ """
2
+ Bridge MCP - Cloud Relay with Persistent Storage
3
+ =================================================
4
+ This MCP server maintains agent registrations across sessions.
5
+
6
+ Key Features:
7
+ - Persistent agent storage (survives restarts)
8
+ - Auto-discovery of local agent
9
+ - Works across all Claude Code/Desktop sessions
10
+ """
11
+
12
+ from fastmcp import FastMCP
13
+ import asyncio
14
+ import json
15
+ import os
16
+ from typing import Optional, Dict, Any
17
+ import httpx
18
+
19
+ # Import our persistent storage
20
+ try:
21
+ from config import agent_storage, config
22
+ except ImportError:
23
+ # Fallback for when running standalone
24
+ import sys
25
+ from pathlib import Path
26
+ sys.path.insert(0, str(Path(__file__).parent))
27
+ from config import agent_storage, config
28
+
29
+ mcp = FastMCP("Bridge MCP")
30
+
31
+ # ============================================
32
+ # AUTO-DISCOVERY ON STARTUP
33
+ # ============================================
34
+
35
+ async def auto_discover_local_agent():
36
+ """
37
+ Automatically discover and register local agent if running.
38
+ Called on server startup.
39
+ """
40
+ local_url = f"http://127.0.0.1:{config.get('local_agent_port', 8006)}"
41
+
42
+ try:
43
+ async with httpx.AsyncClient(timeout=2.0) as client:
44
+ response = await client.get(f"{local_url}/health")
45
+ if response.status_code == 200:
46
+ # Local agent is running! Auto-register if not already
47
+ existing = agent_storage.get("local")
48
+ if not existing or existing.get("callback_url") != local_url:
49
+ agent_storage.register("local", local_url, "Local PC (Auto-discovered)")
50
+ agent_storage.update_status("local", "connected")
51
+ return True
52
+ except:
53
+ pass
54
+ return False
55
+
56
+ # Run auto-discovery on import (when MCP starts)
57
+ try:
58
+ asyncio.get_event_loop().run_until_complete(auto_discover_local_agent())
59
+ except:
60
+ pass # May fail if no event loop yet, that's OK
61
+
62
+ # ============================================
63
+ # RESOURCES API (Desktop Data)
64
+ # ============================================
65
+
66
+ @mcp.resource("desktop://screenshot/latest")
67
+ async def get_latest_screenshot() -> str:
68
+ """Get the most recent screenshot as base64 data."""
69
+ result = await relay_command(None, "screenshot", {})
70
+ if "image" in result:
71
+ return f"data:image/png;base64,{result['image']}"
72
+ return "Screenshot not available"
73
+
74
+ @mcp.resource("desktop://windows")
75
+ async def get_windows_list() -> str:
76
+ """Get list of all open windows."""
77
+ result = await relay_command(None, "get_desktop_state", {})
78
+ if "windows" in result:
79
+ import json
80
+ return json.dumps(result["windows"], indent=2)
81
+ return "No windows data available"
82
+
83
+ @mcp.resource("desktop://logs")
84
+ async def get_agent_logs() -> str:
85
+ """Get recent agent command logs."""
86
+ agents = agent_storage.get_all()
87
+ if not agents:
88
+ return "No agents connected"
89
+
90
+ agent_id = list(agents.keys())[0]
91
+ agent = agents[agent_id]
92
+ callback_url = agent["callback_url"]
93
+
94
+ try:
95
+ async with httpx.AsyncClient(timeout=5.0) as client:
96
+ response = await client.get(f"{callback_url}/logs")
97
+ return response.text
98
+ except:
99
+ return "Logs unavailable"
100
+
101
+ @mcp.resource("file:///{path}")
102
+ async def get_file_content(path: str) -> str:
103
+ """Read content of a file from the desktop."""
104
+ result = await relay_command(None, "file_read", {"path": path})
105
+ if "content" in result:
106
+ return result["content"]
107
+ return f"Error reading file: {result.get('error', 'Unknown error')}"
108
+
109
+ @mcp.resource("desktop://session/context")
110
+ async def get_session_context() -> str:
111
+ """Get recent session history and context."""
112
+ agents = agent_storage.get_all()
113
+ if not agents:
114
+ return "No agents connected"
115
+
116
+ agent_id = list(agents.keys())[0]
117
+ agent = agents[agent_id]
118
+ callback_url = agent["callback_url"]
119
+
120
+ try:
121
+ async with httpx.AsyncClient(timeout=5.0) as client:
122
+ response = await client.get(f"{callback_url}/session/context")
123
+ data = response.json()
124
+ return data.get("summary", "No session context available")
125
+ except:
126
+ return "Session context unavailable"
127
+
128
+ # ============================================
129
+ # PROMPTS API (Workflow Templates)
130
+ # ============================================
131
+
132
+ @mcp.prompt()
133
+ async def automate_desktop_task(task_description: str = "") -> str:
134
+ """Help user automate a desktop task step-by-step."""
135
+ return f"""You are controlling a Windows PC through Bridge MCP.
136
+
137
+ Task: {task_description or "User will describe the task"}
138
+
139
+ Available capabilities:
140
+ - screenshot() - See the screen
141
+ - click(x, y) - Click at coordinates
142
+ - type_text(text) - Type text
143
+ - browser_navigate(url) - Open URLs with Playwright
144
+ - browser_click(selector) - Click web elements
145
+ - get_desktop_state() - See open windows
146
+ - run_powershell(cmd) - Execute commands (requires approval)
147
+
148
+ Instructions:
149
+ 1. Take a screenshot first to see the current state
150
+ 2. Break the task into clear steps
151
+ 3. Execute each step and verify success
152
+ 4. If uncertain, ask the user for clarification
153
+
154
+ Begin by taking a screenshot and analyzing what needs to be done."""
155
+
156
+ @mcp.prompt()
157
+ async def debug_error(error_message: str = "") -> str:
158
+ """Help debug an error message or problem."""
159
+ return f"""You are a debugging assistant with access to a Windows PC.
160
+
161
+ Error/Problem: {error_message or "User will describe the error"}
162
+
163
+ Available tools:
164
+ - screenshot() - See the current screen
165
+ - get_desktop_state() - List open applications
166
+ - file_read(path) - Read log files
167
+ - run_powershell(cmd) - Run diagnostic commands
168
+
169
+ Debugging approach:
170
+ 1. Gather information about the error
171
+ 2. Check relevant logs or files
172
+ 3. Identify the root cause
173
+ 4. Suggest or implement a fix
174
+ 5. Verify the fix worked
175
+
176
+ Start by taking a screenshot to see the current state."""
177
+
178
+ @mcp.prompt()
179
+ async def web_automation(task: str = "", url: str = ""):
180
+ """Automate a web task using Playwright."""
181
+ return f"""You are automating a web browser task.
182
+
183
+ URL: {url or "User will provide"}
184
+ Task: {task or "User will describe"}
185
+
186
+ Playwright tools available:
187
+ - browser_navigate(url) - Go to a page
188
+ - browser_click(selector) - Click elements (use CSS selectors)
189
+ - browser_type(selector, text) - Fill forms
190
+ - browser_press(key) - Press keys like 'Enter'
191
+ - browser_content() - Read page text
192
+ - browser_screenshot() - Capture current page
193
+
194
+ Workflow:
195
+ 1. Navigate to {url or 'the target URL'}
196
+ 2. Wait for page load (1-2 seconds)
197
+ 3. Find and interact with elements using CSS selectors
198
+ 4. Verify success with screenshots or content checks
199
+
200
+ CSS selector tips:
201
+ - #id - for IDs
202
+ - .class - for classes
203
+ - button[type="submit"] - for specific attributes
204
+ - Use browser dev tools to find selectors
205
+
206
+ Begin by navigating to the URL."""
207
+
208
+ # ============================================
209
+ # AGENT REGISTRATION (PERSISTENT)
210
+ # ============================================
211
+
212
+ @mcp.tool
213
+ def register_agent(agent_id: str, callback_url: str, agent_name: str = "My PC") -> dict:
214
+ """
215
+ Register a local agent that will execute commands.
216
+ This registration is PERSISTENT and survives server restarts.
217
+
218
+ Args:
219
+ agent_id: Unique identifier for the agent (e.g., "my-pc", "work-laptop")
220
+ callback_url: URL where the local agent is listening (e.g., http://127.0.0.1:8006)
221
+ agent_name: Friendly name for the agent
222
+
223
+ Returns:
224
+ Registration confirmation
225
+ """
226
+ return agent_storage.register(agent_id, callback_url, agent_name)
227
+
228
+ @mcp.tool
229
+ def unregister_agent(agent_id: str) -> dict:
230
+ """
231
+ Remove a registered agent.
232
+
233
+ Args:
234
+ agent_id: ID of the agent to remove
235
+
236
+ Returns:
237
+ Removal confirmation
238
+ """
239
+ return agent_storage.unregister(agent_id)
240
+
241
+ @mcp.tool
242
+ def list_agents() -> dict:
243
+ """
244
+ List all registered agents (persistent across sessions).
245
+
246
+ Returns:
247
+ Dictionary of all registered agents with their status
248
+ """
249
+ agents = agent_storage.get_all()
250
+ return {
251
+ "agents": [{"id": aid, **info} for aid, info in agents.items()],
252
+ "count": len(agents),
253
+ "config_location": str(agent_storage.agents_file)
254
+ }
255
+
256
+ @mcp.tool
257
+ async def check_agent_health(agent_id: str = None) -> dict:
258
+ """
259
+ Check if an agent is online and responding.
260
+
261
+ Args:
262
+ agent_id: Optional. Agent to check (checks all if not specified)
263
+
264
+ Returns:
265
+ Health status of agent(s)
266
+ """
267
+ results = {}
268
+ agents = agent_storage.get_all()
269
+
270
+ if agent_id:
271
+ if agent_id not in agents:
272
+ return {"error": f"Agent {agent_id} not registered"}
273
+ agents = {agent_id: agents[agent_id]}
274
+
275
+ for aid, info in agents.items():
276
+ try:
277
+ async with httpx.AsyncClient(timeout=5.0) as client:
278
+ response = await client.get(f"{info['callback_url']}/health")
279
+ if response.status_code == 200:
280
+ agent_storage.update_status(aid, "connected")
281
+ results[aid] = {"status": "healthy", "url": info['callback_url']}
282
+ else:
283
+ agent_storage.update_status(aid, "error")
284
+ results[aid] = {"status": "error", "code": response.status_code}
285
+ except httpx.ConnectError:
286
+ agent_storage.update_status(aid, "disconnected")
287
+ results[aid] = {"status": "disconnected", "url": info['callback_url']}
288
+ except Exception as e:
289
+ results[aid] = {"status": "error", "error": str(e)}
290
+
291
+ return results
292
+
293
+ @mcp.tool
294
+ def set_default_agent(agent_id: str) -> dict:
295
+ """
296
+ Set an agent as the default for all commands.
297
+
298
+ Args:
299
+ agent_id: Agent to set as default
300
+
301
+ Returns:
302
+ Confirmation
303
+ """
304
+ return agent_storage.set_default(agent_id)
305
+
306
+ # ============================================
307
+ # COMMAND RELAY (Uses Persistent Storage)
308
+ # ============================================
309
+
310
+ async def relay_command(agent_id: Optional[str], command: str, params: dict) -> dict:
311
+ """
312
+ Relay a command to a local agent.
313
+ Uses persistent storage to find agents.
314
+ """
315
+ agents = agent_storage.get_all()
316
+
317
+ # If no agent specified, try to find one
318
+ if not agent_id:
319
+ # First, try auto-discovery
320
+ if config.get("auto_connect_localhost", True):
321
+ if await auto_discover_local_agent():
322
+ agent_id = "local"
323
+
324
+ # Use first available agent
325
+ if not agent_id and agents:
326
+ agent_id = list(agents.keys())[0]
327
+
328
+ if not agent_id or agent_id not in agents:
329
+ return {
330
+ "error": "No agents available",
331
+ "hint": "Start local_agent.py on your Windows PC. It will auto-register.",
332
+ "registered_agents": list(agents.keys()) if agents else [],
333
+ "config_file": str(agent_storage.agents_file)
334
+ }
335
+
336
+ agent = agents[agent_id]
337
+ callback_url = agent["callback_url"]
338
+ token = agent.get("token")
339
+
340
+ headers = {}
341
+ if token:
342
+ headers["Authorization"] = f"Bearer {token}"
343
+
344
+ try:
345
+ async with httpx.AsyncClient(timeout=config.get("connection_timeout", 30)) as client:
346
+ response = await client.post(
347
+ f"{callback_url}/execute",
348
+ json={"command": command, "params": params},
349
+ headers=headers
350
+ )
351
+ agent_storage.update_status(agent_id, "connected")
352
+ return response.json()
353
+ except httpx.ConnectError:
354
+ agent_storage.update_status(agent_id, "disconnected")
355
+ return {
356
+ "error": f"Cannot connect to agent '{agent_id}' at {callback_url}",
357
+ "hint": "Make sure local_agent.py is running on your PC"
358
+ }
359
+ except Exception as e:
360
+ return {"error": str(e)}
361
+
362
+ # ============================================
363
+ # PC CONTROL TOOLS (Same as before, but using persistent storage)
364
+ # ============================================
365
+
366
+ @mcp.tool
367
+ async def screenshot(agent_id: str = None) -> dict:
368
+ """Take a screenshot of the PC desktop."""
369
+ return await relay_command(agent_id, "screenshot", {})
370
+
371
+ @mcp.tool
372
+ async def click(x: int, y: int, button: str = "left", agent_id: str = None) -> dict:
373
+ """Click at screen coordinates."""
374
+ return await relay_command(agent_id, "click", {"x": x, "y": y, "button": button})
375
+
376
+ @mcp.tool
377
+ async def double_click(x: int, y: int, agent_id: str = None) -> dict:
378
+ """Double-click at screen coordinates."""
379
+ return await relay_command(agent_id, "double_click", {"x": x, "y": y})
380
+
381
+ @mcp.tool
382
+ async def right_click(x: int, y: int, agent_id: str = None) -> dict:
383
+ """Right-click at screen coordinates."""
384
+ return await relay_command(agent_id, "right_click", {"x": x, "y": y})
385
+
386
+ @mcp.tool
387
+ async def type_text(text: str, agent_id: str = None) -> dict:
388
+ """Type text using keyboard."""
389
+ return await relay_command(agent_id, "type_text", {"text": text})
390
+
391
+ @mcp.tool
392
+ async def press_key(key: str, agent_id: str = None) -> dict:
393
+ """Press a keyboard key."""
394
+ return await relay_command(agent_id, "press_key", {"key": key})
395
+
396
+ @mcp.tool
397
+ async def hotkey(keys: str, agent_id: str = None) -> dict:
398
+ """Press a keyboard shortcut (e.g., 'ctrl,c' for copy)."""
399
+ return await relay_command(agent_id, "hotkey", {"keys": keys})
400
+
401
+ @mcp.tool
402
+ async def scroll(direction: str, amount: int = 3, agent_id: str = None) -> dict:
403
+ """Scroll the screen."""
404
+ return await relay_command(agent_id, "scroll", {"direction": direction, "amount": amount})
405
+
406
+ @mcp.tool
407
+ async def move_mouse(x: int, y: int, agent_id: str = None) -> dict:
408
+ """Move mouse to coordinates without clicking."""
409
+ return await relay_command(agent_id, "move_mouse", {"x": x, "y": y})
410
+
411
+ @mcp.tool
412
+ async def drag(start_x: int, start_y: int, end_x: int, end_y: int, agent_id: str = None) -> dict:
413
+ """Drag from one point to another."""
414
+ return await relay_command(agent_id, "drag", {
415
+ "start_x": start_x, "start_y": start_y,
416
+ "end_x": end_x, "end_y": end_y
417
+ })
418
+
419
+ @mcp.tool
420
+ async def get_desktop_state(agent_id: str = None) -> dict:
421
+ """Get the current desktop state including open windows."""
422
+ return await relay_command(agent_id, "get_desktop_state", {})
423
+
424
+ @mcp.tool
425
+ async def get_screen_size(agent_id: str = None) -> dict:
426
+ """Get screen dimensions."""
427
+ return await relay_command(agent_id, "get_screen_size", {})
428
+
429
+ @mcp.tool
430
+ async def get_mouse_position(agent_id: str = None) -> dict:
431
+ """Get current mouse cursor position."""
432
+ return await relay_command(agent_id, "get_mouse_position", {})
433
+
434
+ @mcp.tool
435
+ async def app_launch(name: str, agent_id: str = None) -> dict:
436
+ """Launch an application."""
437
+ return await relay_command(agent_id, "app_launch", {"name": name})
438
+
439
+ @mcp.tool
440
+ async def app_switch(name: str, agent_id: str = None) -> dict:
441
+ """Switch to an open application."""
442
+ return await relay_command(agent_id, "app_switch", {"name": name})
443
+
444
+ @mcp.tool
445
+ async def app_close(name: str, agent_id: str = None) -> dict:
446
+ """Close an application."""
447
+ return await relay_command(agent_id, "app_close", {"name": name})
448
+
449
+ @mcp.tool
450
+ async def app_list(agent_id: str = None) -> dict:
451
+ """List all open applications."""
452
+ return await relay_command(agent_id, "app_list", {})
453
+
454
+ @mcp.tool
455
+ async def run_powershell(command: str, agent_id: str = None) -> dict:
456
+ """Execute a PowerShell command."""
457
+ return await relay_command(agent_id, "run_powershell", {"command": command})
458
+
459
+ @mcp.tool
460
+ async def run_cmd(command: str, agent_id: str = None) -> dict:
461
+ """Execute a CMD command."""
462
+ return await relay_command(agent_id, "run_cmd", {"command": command})
463
+
464
+ @mcp.tool
465
+ async def file_read(path: str, agent_id: str = None) -> dict:
466
+ """Read contents of a file."""
467
+ return await relay_command(agent_id, "file_read", {"path": path})
468
+
469
+ @mcp.tool
470
+ async def file_write(path: str, content: str, agent_id: str = None) -> dict:
471
+ """Write content to a file."""
472
+ return await relay_command(agent_id, "file_write", {"path": path, "content": content})
473
+
474
+ @mcp.tool
475
+ async def file_list(directory: str, agent_id: str = None) -> dict:
476
+ """List files in a directory."""
477
+ return await relay_command(agent_id, "file_list", {"directory": directory})
478
+
479
+ @mcp.tool
480
+ async def clipboard_copy(text: str, agent_id: str = None) -> dict:
481
+ """Copy text to clipboard."""
482
+ return await relay_command(agent_id, "clipboard_copy", {"text": text})
483
+
484
+ @mcp.tool
485
+ async def clipboard_paste(agent_id: str = None) -> dict:
486
+ """Get clipboard contents."""
487
+ return await relay_command(agent_id, "clipboard_paste", {})
488
+
489
+ # ============================================
490
+ # BROWSER TOOLS (Playwright - Advanced)
491
+ # ============================================
492
+
493
+ @mcp.tool
494
+ async def browser_navigate(url: str, agent_id: str = None) -> dict:
495
+ """Navigate to a URL using Playwright (Advanced)."""
496
+ return await relay_command(agent_id, "browser_navigate", {"url": url})
497
+
498
+ @mcp.tool
499
+ async def browser_click(selector: str, agent_id: str = None) -> dict:
500
+ """Click an element by CSS selector."""
501
+ return await relay_command(agent_id, "browser_click", {"selector": selector})
502
+
503
+ @mcp.tool
504
+ async def browser_type(selector: str, text: str, agent_id: str = None) -> dict:
505
+ """Type text into an element by CSS selector."""
506
+ return await relay_command(agent_id, "browser_type", {"selector": selector, "text": text})
507
+
508
+ @mcp.tool
509
+ async def browser_press(key: str, agent_id: str = None) -> dict:
510
+ """Press a key (e.g., 'Enter', 'ArrowDown')."""
511
+ return await relay_command(agent_id, "browser_press", {"key": key})
512
+
513
+ @mcp.tool
514
+ async def browser_screenshot(agent_id: str = None) -> dict:
515
+ """Take a screenshot of the browser page."""
516
+ return await relay_command(agent_id, "browser_screenshot", {})
517
+
518
+ @mcp.tool
519
+ async def browser_content(agent_id: str = None) -> dict:
520
+ """Get the text content of the browser page."""
521
+ return await relay_command(agent_id, "browser_content", {})
522
+
523
+ @mcp.tool
524
+ async def chrome_open(url: str = None, agent_id: str = None) -> dict:
525
+ """Open Chrome browser."""
526
+ return await relay_command(agent_id, "chrome_open", {"url": url})
527
+
528
+ @mcp.tool
529
+ async def chrome_navigate(url: str, agent_id: str = None) -> dict:
530
+ """Navigate to a URL in Chrome."""
531
+ return await relay_command(agent_id, "chrome_navigate", {"url": url})
532
+
533
+ @mcp.tool
534
+ async def wait(seconds: float, agent_id: str = None) -> dict:
535
+ """Wait for specified seconds."""
536
+ return await relay_command(agent_id, "wait", {"seconds": seconds})
537
+
538
+ # ============================================
539
+ # INFO TOOLS
540
+ # ============================================
541
+
542
+ @mcp.tool
543
+ def get_info() -> dict:
544
+ """Get information about Bridge MCP."""
545
+ agents = agent_storage.get_all()
546
+ return {
547
+ "name": "Bridge MCP",
548
+ "version": "2.0.0",
549
+ "description": "Universal PC Control for Any AI - with Persistent Storage",
550
+ "architecture": "Cloud Relay + Local Agent + Persistent Config",
551
+ "connected_agents": len(agents),
552
+ "agents": list(agents.keys()),
553
+ "config_directory": str(agent_storage.agents_file.parent),
554
+ "auto_discovery": config.get("auto_connect_localhost", True),
555
+ "github": "https://github.com/BarhamAgha1/Bridge-MCP"
556
+ }
557
+
558
+ @mcp.tool
559
+ def get_setup_instructions() -> str:
560
+ """Get setup instructions for Bridge MCP."""
561
+ return """
562
+ Bridge MCP Setup Instructions (v2.0 - Persistent Storage)
563
+ ==========================================================
564
+
565
+ QUICK START (Recommended):
566
+ --------------------------
567
+ 1. Run local_agent.py on your Windows PC (keep it running)
568
+ 2. That's it! Bridge MCP auto-discovers and remembers your agent.
569
+
570
+ The agent registration is PERSISTENT - it survives across:
571
+ - Claude Code sessions
572
+ - Claude Desktop restarts
573
+ - Computer reboots (as long as local_agent.py is running)
574
+
575
+ MANUAL REGISTRATION (if needed):
576
+ ---------------------------------
577
+ Use register_agent("my-pc", "http://127.0.0.1:8006", "My PC")
578
+
579
+ CHECK STATUS:
580
+ -------------
581
+ - list_agents() - See all registered agents
582
+ - check_agent_health() - Verify agents are online
583
+
584
+ CONFIG LOCATION:
585
+ ----------------
586
+ Windows: %APPDATA%/bridge-mcp/agents.json
587
+ Linux/Mac: ~/.config/bridge-mcp/agents.json
588
+ """
589
+
590
+ # ============================================
591
+ # ENTRY POINT
592
+ # ============================================
593
+
594
+ if __name__ == "__main__":
595
+ mcp.run()