@remcp/remcp 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/bin/remcp.mjs +3 -0
- package/chatgpt-app-submission.json +356 -0
- package/package.json +63 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/app.js +95 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/icon-120.png +0 -0
- package/public/assets/icon-16.png +0 -0
- package/public/assets/icon-180.png +0 -0
- package/public/assets/icon-192.png +0 -0
- package/public/assets/icon-256.png +0 -0
- package/public/assets/icon-32.png +0 -0
- package/public/assets/icon-48.png +0 -0
- package/public/assets/icon-512.png +0 -0
- package/public/assets/og-remcp.png +0 -0
- package/public/authorize.html +70 -0
- package/public/docs.html +58 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-48x48.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +130 -0
- package/public/oauth-app.js +54 -0
- package/public/privacy.html +61 -0
- package/public/remcp-darkmode-16x16.png +0 -0
- package/public/remcp-darkmode-192x192.png +0 -0
- package/public/remcp-darkmode-32x32.png +0 -0
- package/public/remcp-darkmode-48x48.png +0 -0
- package/public/remcp-darkmode-512x512.png +0 -0
- package/public/security.html +59 -0
- package/public/site.webmanifest +23 -0
- package/public/style.css +231 -0
- package/public/support.html +57 -0
- package/public/terms.html +62 -0
- package/skills/remcp-operator/SKILL.md +36 -0
- package/src/agent.mjs +89 -0
- package/src/auth.mjs +70 -0
- package/src/cli.mjs +90 -0
- package/src/config.mjs +51 -0
- package/src/db.mjs +99 -0
- package/src/mcp.mjs +90 -0
- package/src/oauth.mjs +145 -0
- package/src/relay.mjs +97 -0
- package/src/review-sandbox.mjs +96 -0
- package/src/server.mjs +158 -0
- package/src/tool-catalog.json +836 -0
- package/src/util.mjs +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anton Baider
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# ReMCP
|
|
2
|
+
|
|
3
|
+
ReMCP is an open-source remote MCP bridge for computers you control. A local agent connects outbound to a ReMCP server and exposes filesystem, search, terminal and process tools through one authenticated remote MCP endpoint.
|
|
4
|
+
|
|
5
|
+
**Hosted service:** https://remcp.delio24.com
|
|
6
|
+
**MCP endpoint:** `https://remcp.delio24.com/mcp`
|
|
7
|
+
|
|
8
|
+
## How it works
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
ChatGPT / MCP client ── OAuth + HTTPS ──> ReMCP server
|
|
12
|
+
│
|
|
13
|
+
├── WSS ──> laptop agent ──> local MCP
|
|
14
|
+
└── WSS ──> server agent ──> local MCP
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The central service stores account/device metadata, hashed reusable credentials and OAuth state. Actual filesystem and process access stays on the paired computer.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Requires Node.js 22.5 or newer.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @remcp/remcp connect --server https://remcp.delio24.com --code YOUR_PAIRING_CODE --install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Generate the one-time pairing code after signing in at https://remcp.delio24.com.
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
remcp connect Pair this computer with a ReMCP server
|
|
32
|
+
remcp start Run the device agent in the foreground
|
|
33
|
+
remcp status Check the saved configuration and server
|
|
34
|
+
remcp install Install the user service where supported
|
|
35
|
+
remcp uninstall Remove the installed user service
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Self-host the server
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/antonbaider/remcp.git
|
|
42
|
+
cd remcp
|
|
43
|
+
cp .env.example .env
|
|
44
|
+
# Fill in your public URL and Firebase Web App configuration.
|
|
45
|
+
docker compose up -d --build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Put a TLS reverse proxy in front of ReMCP and proxy WebSocket upgrades for `/agent`. The example Compose file binds the application to localhost by default. Do not publish the internal application port directly unless you have deliberately secured it.
|
|
49
|
+
|
|
50
|
+
ReMCP does **not** require a Firebase Admin service-account private key. Firebase ID tokens are verified using Google's public signing keys.
|
|
51
|
+
|
|
52
|
+
## ChatGPT / remote MCP
|
|
53
|
+
|
|
54
|
+
ReMCP exposes Streamable HTTP MCP at `/mcp`. Authentication uses authorization code + PKCE, Dynamic Client Registration, short-lived access tokens and rotating refresh tokens.
|
|
55
|
+
|
|
56
|
+
The repository includes `chatgpt-app-submission.json` plus a reusable ReMCP operator skill for submission/testing workflows.
|
|
57
|
+
## Security model
|
|
58
|
+
|
|
59
|
+
- one independent credential per paired device;
|
|
60
|
+
- device and refresh credentials are stored as one-way hashes server-side;
|
|
61
|
+
- pairing codes are short-lived and single-use;
|
|
62
|
+
- MCP sessions are bound to the authenticated account;
|
|
63
|
+
- the device agent connects outbound over WSS;
|
|
64
|
+
- write/process tools expose explicit MCP risk annotations;
|
|
65
|
+
- the public review sandbox is isolated per authenticated user and intentionally limits terminal commands;
|
|
66
|
+
- central Docker deployment runs read-only with dropped Linux capabilities in the supplied Compose configuration.
|
|
67
|
+
|
|
68
|
+
See https://remcp.delio24.com/security for the hosted-service security overview.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm ci
|
|
74
|
+
npm run check
|
|
75
|
+
npm test
|
|
76
|
+
npm audit --omit=dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
ReMCP is MIT licensed. `@wonderwhy-er/desktop-commander` is a separate MIT-licensed dependency; its notices are preserved in `THIRD_PARTY_NOTICES.md`.
|
|
82
|
+
|
|
83
|
+
ReMCP does not require or depend on the proprietary Remote Desktop Commander hosted relay service.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
ReMCP uses `@wonderwhy-er/desktop-commander` as its local device MCP backend. Desktop Commander is MIT licensed.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2024-2025 Eduard Ruzga and Desktop Commander Contributors.
|
|
6
|
+
|
|
7
|
+
The upstream project is available at https://github.com/wonderwhy-er/DesktopCommanderMCP. ReMCP does not use or depend on the proprietary Remote Desktop Commander cloud relay.
|
package/bin/remcp.mjs
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://developers.openai.com/apps-sdk/schemas/chatgpt-app-submission.v1.json",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"app_info": {
|
|
5
|
+
"display_name": "ReMCP",
|
|
6
|
+
"subtitle": "Control your own computers",
|
|
7
|
+
"description": "ReMCP lets authenticated users connect to computers they have paired and work with local files, directories, searches, terminal sessions, and processes through MCP. Each device uses a revocable credential and an outbound agent connection.",
|
|
8
|
+
"category": "DEVELOPER_TOOLS"
|
|
9
|
+
},
|
|
10
|
+
"tools": {
|
|
11
|
+
"list_devices": {
|
|
12
|
+
"annotations": {
|
|
13
|
+
"readOnlyHint": true,
|
|
14
|
+
"openWorldHint": false,
|
|
15
|
+
"destructiveHint": false
|
|
16
|
+
},
|
|
17
|
+
"justifications": {
|
|
18
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
19
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
20
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"get_config": {
|
|
24
|
+
"annotations": {
|
|
25
|
+
"readOnlyHint": true,
|
|
26
|
+
"openWorldHint": false,
|
|
27
|
+
"destructiveHint": false
|
|
28
|
+
},
|
|
29
|
+
"justifications": {
|
|
30
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
31
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
32
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"set_config_value": {
|
|
36
|
+
"annotations": {
|
|
37
|
+
"readOnlyHint": false,
|
|
38
|
+
"openWorldHint": false,
|
|
39
|
+
"destructiveHint": true
|
|
40
|
+
},
|
|
41
|
+
"justifications": {
|
|
42
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
43
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
44
|
+
"destructive_justification": "Can change security and access configuration on the selected computer."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"read_file": {
|
|
48
|
+
"annotations": {
|
|
49
|
+
"readOnlyHint": true,
|
|
50
|
+
"openWorldHint": false,
|
|
51
|
+
"destructiveHint": false
|
|
52
|
+
},
|
|
53
|
+
"justifications": {
|
|
54
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
55
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
56
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"read_multiple_files": {
|
|
60
|
+
"annotations": {
|
|
61
|
+
"readOnlyHint": true,
|
|
62
|
+
"openWorldHint": false,
|
|
63
|
+
"destructiveHint": false
|
|
64
|
+
},
|
|
65
|
+
"justifications": {
|
|
66
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
67
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
68
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"write_file": {
|
|
72
|
+
"annotations": {
|
|
73
|
+
"readOnlyHint": false,
|
|
74
|
+
"openWorldHint": false,
|
|
75
|
+
"destructiveHint": true
|
|
76
|
+
},
|
|
77
|
+
"justifications": {
|
|
78
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
79
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
80
|
+
"destructive_justification": "Can overwrite or append file content on the selected computer."
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"write_pdf": {
|
|
84
|
+
"annotations": {
|
|
85
|
+
"readOnlyHint": false,
|
|
86
|
+
"openWorldHint": false,
|
|
87
|
+
"destructiveHint": true
|
|
88
|
+
},
|
|
89
|
+
"justifications": {
|
|
90
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
91
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
92
|
+
"destructive_justification": "Can replace PDF content or remove pages when editing a PDF."
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"create_directory": {
|
|
96
|
+
"annotations": {
|
|
97
|
+
"readOnlyHint": false,
|
|
98
|
+
"openWorldHint": false,
|
|
99
|
+
"destructiveHint": false
|
|
100
|
+
},
|
|
101
|
+
"justifications": {
|
|
102
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
103
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
104
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"list_directory": {
|
|
108
|
+
"annotations": {
|
|
109
|
+
"readOnlyHint": true,
|
|
110
|
+
"openWorldHint": false,
|
|
111
|
+
"destructiveHint": false
|
|
112
|
+
},
|
|
113
|
+
"justifications": {
|
|
114
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
115
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
116
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"move_file": {
|
|
120
|
+
"annotations": {
|
|
121
|
+
"readOnlyHint": false,
|
|
122
|
+
"openWorldHint": false,
|
|
123
|
+
"destructiveHint": true
|
|
124
|
+
},
|
|
125
|
+
"justifications": {
|
|
126
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
127
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
128
|
+
"destructive_justification": "Moves or renames filesystem objects and may replace an intended path."
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"start_search": {
|
|
132
|
+
"annotations": {
|
|
133
|
+
"readOnlyHint": false,
|
|
134
|
+
"openWorldHint": false,
|
|
135
|
+
"destructiveHint": false
|
|
136
|
+
},
|
|
137
|
+
"justifications": {
|
|
138
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
139
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
140
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"get_more_search_results": {
|
|
144
|
+
"annotations": {
|
|
145
|
+
"readOnlyHint": true,
|
|
146
|
+
"openWorldHint": false,
|
|
147
|
+
"destructiveHint": false
|
|
148
|
+
},
|
|
149
|
+
"justifications": {
|
|
150
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
151
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
152
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"stop_search": {
|
|
156
|
+
"annotations": {
|
|
157
|
+
"readOnlyHint": false,
|
|
158
|
+
"openWorldHint": false,
|
|
159
|
+
"destructiveHint": false
|
|
160
|
+
},
|
|
161
|
+
"justifications": {
|
|
162
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
163
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
164
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"list_searches": {
|
|
168
|
+
"annotations": {
|
|
169
|
+
"readOnlyHint": true,
|
|
170
|
+
"openWorldHint": false,
|
|
171
|
+
"destructiveHint": false
|
|
172
|
+
},
|
|
173
|
+
"justifications": {
|
|
174
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
175
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
176
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"get_file_info": {
|
|
180
|
+
"annotations": {
|
|
181
|
+
"readOnlyHint": true,
|
|
182
|
+
"openWorldHint": false,
|
|
183
|
+
"destructiveHint": false
|
|
184
|
+
},
|
|
185
|
+
"justifications": {
|
|
186
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
187
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
188
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"edit_block": {
|
|
192
|
+
"annotations": {
|
|
193
|
+
"readOnlyHint": false,
|
|
194
|
+
"openWorldHint": false,
|
|
195
|
+
"destructiveHint": true
|
|
196
|
+
},
|
|
197
|
+
"justifications": {
|
|
198
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
199
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
200
|
+
"destructive_justification": "Changes existing file or document content on the selected computer."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"start_process": {
|
|
204
|
+
"annotations": {
|
|
205
|
+
"readOnlyHint": false,
|
|
206
|
+
"openWorldHint": true,
|
|
207
|
+
"destructiveHint": true
|
|
208
|
+
},
|
|
209
|
+
"justifications": {
|
|
210
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
211
|
+
"open_world_justification": "User-supplied process commands or input can contact external services or change external state.",
|
|
212
|
+
"destructive_justification": "Can execute commands that modify files, processes, configuration, or external systems."
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"read_process_output": {
|
|
216
|
+
"annotations": {
|
|
217
|
+
"readOnlyHint": true,
|
|
218
|
+
"openWorldHint": false,
|
|
219
|
+
"destructiveHint": false
|
|
220
|
+
},
|
|
221
|
+
"justifications": {
|
|
222
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
223
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
224
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"interact_with_process": {
|
|
228
|
+
"annotations": {
|
|
229
|
+
"readOnlyHint": false,
|
|
230
|
+
"openWorldHint": true,
|
|
231
|
+
"destructiveHint": true
|
|
232
|
+
},
|
|
233
|
+
"justifications": {
|
|
234
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
235
|
+
"open_world_justification": "User-supplied process commands or input can contact external services or change external state.",
|
|
236
|
+
"destructive_justification": "Can send commands or input to a process that performs irreversible actions."
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"force_terminate": {
|
|
240
|
+
"annotations": {
|
|
241
|
+
"readOnlyHint": false,
|
|
242
|
+
"openWorldHint": false,
|
|
243
|
+
"destructiveHint": true
|
|
244
|
+
},
|
|
245
|
+
"justifications": {
|
|
246
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
247
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
248
|
+
"destructive_justification": "Force-stops a running terminal session."
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"list_sessions": {
|
|
252
|
+
"annotations": {
|
|
253
|
+
"readOnlyHint": true,
|
|
254
|
+
"openWorldHint": false,
|
|
255
|
+
"destructiveHint": false
|
|
256
|
+
},
|
|
257
|
+
"justifications": {
|
|
258
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
259
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
260
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"list_processes": {
|
|
264
|
+
"annotations": {
|
|
265
|
+
"readOnlyHint": true,
|
|
266
|
+
"openWorldHint": false,
|
|
267
|
+
"destructiveHint": false
|
|
268
|
+
},
|
|
269
|
+
"justifications": {
|
|
270
|
+
"read_only_justification": "Only retrieves local device data or results and does not change persistent user state.",
|
|
271
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
272
|
+
"destructive_justification": "Does not delete, overwrite, revoke, terminate, or perform another irreversible action by itself."
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"kill_process": {
|
|
276
|
+
"annotations": {
|
|
277
|
+
"readOnlyHint": false,
|
|
278
|
+
"openWorldHint": false,
|
|
279
|
+
"destructiveHint": true
|
|
280
|
+
},
|
|
281
|
+
"justifications": {
|
|
282
|
+
"read_only_justification": "Creates, changes, stops, or controls local device state or a local execution session.",
|
|
283
|
+
"open_world_justification": "Operates on the paired computer or ReMCP session and does not itself publish or modify third-party internet state.",
|
|
284
|
+
"destructive_justification": "Terminates the selected operating-system process."
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"test_cases": [
|
|
289
|
+
{
|
|
290
|
+
"description": "List the computers available to the signed-in ReMCP account.",
|
|
291
|
+
"user_prompt": "List my ReMCP devices and tell me which ones are online. Include the ReMCP Review Sandbox if it is available.",
|
|
292
|
+
"file_attachment_urls": null,
|
|
293
|
+
"tools_triggered": "list_devices",
|
|
294
|
+
"expected_output": "Returns devices available to the authenticated account and includes the isolated ReMCP Review Sandbox with device id review-sandbox.",
|
|
295
|
+
"expected_output_url": null
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"description": "Browse a directory on a paired review computer.",
|
|
299
|
+
"user_prompt": "Using ReMCP device id review-sandbox, list the files in /workspace.",
|
|
300
|
+
"file_attachment_urls": null,
|
|
301
|
+
"tools_triggered": "list_directory",
|
|
302
|
+
"expected_output": "Returns the isolated sandbox directory entries without changing them.",
|
|
303
|
+
"expected_output_url": null
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"description": "Read a text file from a paired review computer.",
|
|
307
|
+
"user_prompt": "Using ReMCP device id review-sandbox, read /workspace/README.txt and summarize it.",
|
|
308
|
+
"file_attachment_urls": null,
|
|
309
|
+
"tools_triggered": "read_file",
|
|
310
|
+
"expected_output": "Returns the seeded review-sandbox README content so it can be summarized.",
|
|
311
|
+
"expected_output_url": null
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"description": "Create a new file on a paired review computer.",
|
|
315
|
+
"user_prompt": "Using ReMCP device id review-sandbox, create /workspace/review-note.txt containing exactly: ReMCP submission test.",
|
|
316
|
+
"file_attachment_urls": null,
|
|
317
|
+
"tools_triggered": "write_file",
|
|
318
|
+
"expected_output": "Creates the requested file only inside the authenticated reviewer sandbox and reports the write result.",
|
|
319
|
+
"expected_output_url": null
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"description": "Run a harmless terminal command on a paired review computer.",
|
|
323
|
+
"user_prompt": "Using ReMCP device id review-sandbox, run pwd and show me the result.",
|
|
324
|
+
"file_attachment_urls": null,
|
|
325
|
+
"tools_triggered": "start_process",
|
|
326
|
+
"expected_output": "Runs the sandbox-safe command and returns /workspace; arbitrary terminal commands remain blocked in the review sandbox.",
|
|
327
|
+
"expected_output_url": null
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
"negative_test_cases": [
|
|
331
|
+
{
|
|
332
|
+
"description": "Do not invoke ReMCP for general weather information.",
|
|
333
|
+
"user_prompt": "What will the weather be in Warsaw tomorrow?",
|
|
334
|
+
"file_attachment_urls": null,
|
|
335
|
+
"tools_triggered": null,
|
|
336
|
+
"expected_output": "ReMCP should not be invoked because the request does not involve a paired computer.",
|
|
337
|
+
"expected_output_url": null
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"description": "Do not invoke ReMCP for standalone writing help.",
|
|
341
|
+
"user_prompt": "Draft a polite email asking my landlord to repair a leaking faucet.",
|
|
342
|
+
"file_attachment_urls": null,
|
|
343
|
+
"tools_triggered": null,
|
|
344
|
+
"expected_output": "ReMCP should not be invoked because no local computer action or data is needed.",
|
|
345
|
+
"expected_output_url": null
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"description": "Do not invoke ReMCP for a conceptual technical explanation.",
|
|
349
|
+
"user_prompt": "Explain OAuth PKCE in simple terms.",
|
|
350
|
+
"file_attachment_urls": null,
|
|
351
|
+
"tools_triggered": null,
|
|
352
|
+
"expected_output": "ReMCP should not be invoked because the user only requested an explanation.",
|
|
353
|
+
"expected_output_url": null
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remcp/remcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Self-hosted remote MCP bridge for securely controlling your own computers from MCP clients.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://remcp.delio24.com",
|
|
8
|
+
"bin": {
|
|
9
|
+
"remcp": "bin/remcp.mjs"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22.5.0"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"src",
|
|
17
|
+
"public",
|
|
18
|
+
"skills/remcp-operator",
|
|
19
|
+
"chatgpt-app-submission.json",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"THIRD_PARTY_NOTICES.md",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"server": "node src/server.mjs",
|
|
26
|
+
"agent": "node src/agent.mjs",
|
|
27
|
+
"check": "node --check src/server.mjs && node --check src/agent.mjs && node --check src/cli.mjs",
|
|
28
|
+
"test": "node --test test/*.test.mjs",
|
|
29
|
+
"submission": "node scripts/generate-submission.mjs"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
33
|
+
"@wonderwhy-er/desktop-commander": "^0.2.50",
|
|
34
|
+
"express": "^5.2.1",
|
|
35
|
+
"express-rate-limit": "^8.7.0",
|
|
36
|
+
"helmet": "^8.3.0",
|
|
37
|
+
"jose": "^6.2.12",
|
|
38
|
+
"ws": "^8.21.3"
|
|
39
|
+
},
|
|
40
|
+
"overrides": {
|
|
41
|
+
"sharp": "0.35.4",
|
|
42
|
+
"uuid": "11.1.1"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/antonbaider/remcp.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/antonbaider/remcp/issues"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"mcp",
|
|
56
|
+
"remote",
|
|
57
|
+
"computer",
|
|
58
|
+
"filesystem",
|
|
59
|
+
"terminal",
|
|
60
|
+
"chatgpt",
|
|
61
|
+
"self-hosted"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
Binary file
|
|
Binary file
|
package/public/app.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js';
|
|
2
|
+
import { getAuth, GoogleAuthProvider, onAuthStateChanged, signInWithPopup, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js';
|
|
3
|
+
|
|
4
|
+
const cfg = await fetch(`/api/config?fresh=${Date.now()}`, { cache: 'no-store' }).then(r => r.json());
|
|
5
|
+
const auth = getAuth(initializeApp(cfg.firebase));
|
|
6
|
+
const $ = selector => document.querySelector(selector);
|
|
7
|
+
const showError = error => { $('#auth-error').textContent = error?.message || String(error); };
|
|
8
|
+
const googleProvider = new GoogleAuthProvider();
|
|
9
|
+
|
|
10
|
+
$('#mcp-url').textContent = cfg.mcpUrl;
|
|
11
|
+
$('#google').onclick = async () => {
|
|
12
|
+
const button = $('#google');
|
|
13
|
+
const label = button.querySelector('span');
|
|
14
|
+
button.disabled = true;
|
|
15
|
+
$('#auth-error').textContent = '';
|
|
16
|
+
label.textContent = 'Opening Google…';
|
|
17
|
+
try { await signInWithPopup(auth, googleProvider); }
|
|
18
|
+
catch (error) { showError(error); }
|
|
19
|
+
finally { button.disabled = false; label.textContent = 'Continue with Google'; }
|
|
20
|
+
};
|
|
21
|
+
$('#email-login').onclick = () => signInWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
|
|
22
|
+
$('#email-register').onclick = () => createUserWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
|
|
23
|
+
$('#logout').onclick = () => signOut(auth);
|
|
24
|
+
|
|
25
|
+
document.addEventListener('click', async event => {
|
|
26
|
+
const button = event.target.closest('[data-copy]');
|
|
27
|
+
if (!button) return;
|
|
28
|
+
const target = document.querySelector(button.dataset.copy);
|
|
29
|
+
if (!target) return;
|
|
30
|
+
await navigator.clipboard.writeText(target.textContent.trim());
|
|
31
|
+
const original = button.textContent;
|
|
32
|
+
button.textContent = 'Copied';
|
|
33
|
+
setTimeout(() => { button.textContent = original; }, 1200);
|
|
34
|
+
});
|
|
35
|
+
async function api(path, options = {}) {
|
|
36
|
+
const token = await auth.currentUser.getIdToken();
|
|
37
|
+
const response = await fetch(path, { ...options, headers: { ...(options.headers || {}), authorization: `Bearer ${token}`, 'content-type': 'application/json' } });
|
|
38
|
+
if (!response.ok) throw new Error(await response.text());
|
|
39
|
+
return response.status === 204 ? null : response.json();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function deviceRow(device) {
|
|
43
|
+
const row = document.createElement('div');
|
|
44
|
+
row.className = 'device';
|
|
45
|
+
const details = document.createElement('div');
|
|
46
|
+
const name = document.createElement('strong');
|
|
47
|
+
name.textContent = device.name || 'Unnamed device';
|
|
48
|
+
const meta = document.createElement('div');
|
|
49
|
+
meta.className = 'muted';
|
|
50
|
+
meta.textContent = `${device.platform || 'unknown'} · ${device.hostname || device.id}`;
|
|
51
|
+
const status = document.createElement('div');
|
|
52
|
+
status.className = device.online ? 'online' : 'offline';
|
|
53
|
+
status.textContent = device.online ? 'Online' : 'Offline';
|
|
54
|
+
details.append(name, meta, status);
|
|
55
|
+
const revoke = document.createElement('button');
|
|
56
|
+
revoke.className = 'danger';
|
|
57
|
+
revoke.type = 'button';
|
|
58
|
+
revoke.textContent = 'Revoke';
|
|
59
|
+
revoke.setAttribute('aria-label', `Revoke ${device.name || 'device'}`);
|
|
60
|
+
revoke.onclick = async () => {
|
|
61
|
+
if (!confirm(`Revoke ${device.name || 'this device'}? It will need to be paired again before reconnecting.`)) return;
|
|
62
|
+
await api(`/api/devices/${encodeURIComponent(device.id)}`, { method: 'DELETE' });
|
|
63
|
+
await refreshDevices();
|
|
64
|
+
};
|
|
65
|
+
row.append(details, revoke);
|
|
66
|
+
return row;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function refreshDevices() {
|
|
70
|
+
const data = await api('/api/me');
|
|
71
|
+
const root = $('#devices');
|
|
72
|
+
root.replaceChildren();
|
|
73
|
+
if (!data.devices.length) { root.textContent = 'No devices yet.'; return; }
|
|
74
|
+
for (const device of data.devices) root.append(deviceRow(device));
|
|
75
|
+
}
|
|
76
|
+
$('#pair').onclick = async () => {
|
|
77
|
+
try {
|
|
78
|
+
const pairing = await api('/api/pair/create', { method: 'POST', body: '{}' });
|
|
79
|
+
$('#pairing').classList.remove('hidden');
|
|
80
|
+
$('#pair-command').textContent = pairing.command;
|
|
81
|
+
const expiry = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(new Date(pairing.expiresAt));
|
|
82
|
+
$('#pair-expiry').textContent = `Expires ${expiry}`;
|
|
83
|
+
} catch (error) { showError(error); }
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
onAuthStateChanged(auth, async user => {
|
|
87
|
+
const signedIn = Boolean(user);
|
|
88
|
+
$('#signed-out').classList.toggle('hidden', signedIn);
|
|
89
|
+
$('#signed-in').classList.toggle('hidden', !signedIn);
|
|
90
|
+
$('#dashboard').classList.toggle('hidden', !signedIn);
|
|
91
|
+
$('#auth-error').textContent = '';
|
|
92
|
+
if (!signedIn) return;
|
|
93
|
+
$('#user-label').textContent = user.email || user.uid;
|
|
94
|
+
try { await refreshDevices(); } catch (error) { showError(error); }
|
|
95
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|