@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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/THIRD_PARTY_NOTICES.md +7 -0
  4. package/bin/remcp.mjs +3 -0
  5. package/chatgpt-app-submission.json +356 -0
  6. package/package.json +63 -0
  7. package/public/android-chrome-192x192.png +0 -0
  8. package/public/android-chrome-512x512.png +0 -0
  9. package/public/app.js +95 -0
  10. package/public/apple-touch-icon.png +0 -0
  11. package/public/assets/icon-120.png +0 -0
  12. package/public/assets/icon-16.png +0 -0
  13. package/public/assets/icon-180.png +0 -0
  14. package/public/assets/icon-192.png +0 -0
  15. package/public/assets/icon-256.png +0 -0
  16. package/public/assets/icon-32.png +0 -0
  17. package/public/assets/icon-48.png +0 -0
  18. package/public/assets/icon-512.png +0 -0
  19. package/public/assets/og-remcp.png +0 -0
  20. package/public/authorize.html +70 -0
  21. package/public/docs.html +58 -0
  22. package/public/favicon-16x16.png +0 -0
  23. package/public/favicon-32x32.png +0 -0
  24. package/public/favicon-48x48.png +0 -0
  25. package/public/favicon.ico +0 -0
  26. package/public/index.html +130 -0
  27. package/public/oauth-app.js +54 -0
  28. package/public/privacy.html +61 -0
  29. package/public/remcp-darkmode-16x16.png +0 -0
  30. package/public/remcp-darkmode-192x192.png +0 -0
  31. package/public/remcp-darkmode-32x32.png +0 -0
  32. package/public/remcp-darkmode-48x48.png +0 -0
  33. package/public/remcp-darkmode-512x512.png +0 -0
  34. package/public/security.html +59 -0
  35. package/public/site.webmanifest +23 -0
  36. package/public/style.css +231 -0
  37. package/public/support.html +57 -0
  38. package/public/terms.html +62 -0
  39. package/skills/remcp-operator/SKILL.md +36 -0
  40. package/src/agent.mjs +89 -0
  41. package/src/auth.mjs +70 -0
  42. package/src/cli.mjs +90 -0
  43. package/src/config.mjs +51 -0
  44. package/src/db.mjs +99 -0
  45. package/src/mcp.mjs +90 -0
  46. package/src/oauth.mjs +145 -0
  47. package/src/relay.mjs +97 -0
  48. package/src/review-sandbox.mjs +96 -0
  49. package/src/server.mjs +158 -0
  50. package/src/tool-catalog.json +836 -0
  51. 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,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.mjs';
3
+ main().catch(error => { console.error(`ReMCP: ${error.message}`); process.exit(1); });
@@ -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
+ }
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