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.
- bridge_mcp-1.0.0.dist-info/METADATA +521 -0
- bridge_mcp-1.0.0.dist-info/RECORD +5 -0
- bridge_mcp-1.0.0.dist-info/WHEEL +4 -0
- bridge_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
- bridge_mcp.py +595 -0
|
@@ -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
|
+
[](https://fastmcp.cloud)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](https://python.org)
|
|
28
|
+
[](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,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()
|