@remcp/remcp 0.1.3 → 0.2.4

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 (56) hide show
  1. package/README.md +41 -113
  2. package/package.json +10 -24
  3. package/src/agent.mjs +215 -36
  4. package/src/cli.mjs +217 -43
  5. package/src/runtime.mjs +15 -0
  6. package/src/version.mjs +1 -3
  7. package/THIRD_PARTY_NOTICES.md +0 -7
  8. package/assets/remcp-icon.png +0 -0
  9. package/mcp.json +0 -9
  10. package/plugin.json +0 -37
  11. package/public/android-chrome-192x192.png +0 -0
  12. package/public/android-chrome-512x512.png +0 -0
  13. package/public/app.js +0 -96
  14. package/public/apple-touch-icon.png +0 -0
  15. package/public/assets/art/hero-relay.webp +0 -0
  16. package/public/assets/art/relay-detail.webp +0 -0
  17. package/public/assets/icon-120.png +0 -0
  18. package/public/assets/icon-16.png +0 -0
  19. package/public/assets/icon-180.png +0 -0
  20. package/public/assets/icon-192.png +0 -0
  21. package/public/assets/icon-256.png +0 -0
  22. package/public/assets/icon-32.png +0 -0
  23. package/public/assets/icon-48.png +0 -0
  24. package/public/assets/icon-512.png +0 -0
  25. package/public/assets/og-remcp.png +0 -0
  26. package/public/authorize.html +0 -59
  27. package/public/copy.js +0 -40
  28. package/public/docs.html +0 -93
  29. package/public/favicon-16x16.png +0 -0
  30. package/public/favicon-32x32.png +0 -0
  31. package/public/favicon-48x48.png +0 -0
  32. package/public/favicon.ico +0 -0
  33. package/public/index.html +0 -134
  34. package/public/oauth-app.js +0 -63
  35. package/public/privacy.html +0 -80
  36. package/public/remcp-darkmode-16x16.png +0 -0
  37. package/public/remcp-darkmode-192x192.png +0 -0
  38. package/public/remcp-darkmode-32x32.png +0 -0
  39. package/public/remcp-darkmode-48x48.png +0 -0
  40. package/public/remcp-darkmode-512x512.png +0 -0
  41. package/public/security.html +0 -70
  42. package/public/site.webmanifest +0 -23
  43. package/public/style.css +0 -261
  44. package/public/support.html +0 -75
  45. package/public/terms.html +0 -77
  46. package/skills/remcp-operator/SKILL.md +0 -36
  47. package/src/auth.mjs +0 -71
  48. package/src/config.mjs +0 -51
  49. package/src/db.mjs +0 -102
  50. package/src/mcp.mjs +0 -95
  51. package/src/oauth.mjs +0 -164
  52. package/src/relay.mjs +0 -97
  53. package/src/review-sandbox.mjs +0 -96
  54. package/src/server.mjs +0 -159
  55. package/src/tool-catalog.json +0 -796
  56. package/src/util.mjs +0 -24
@@ -1,23 +0,0 @@
1
- {
2
- "name": "ReMCP",
3
- "short_name": "ReMCP",
4
- "description": "Secure remote MCP access to computers you pair.",
5
- "start_url": "/",
6
- "display": "standalone",
7
- "background_color": "#f7f6f2",
8
- "theme_color": "#111114",
9
- "icons": [
10
- {
11
- "src": "/android-chrome-192x192.png",
12
- "sizes": "192x192",
13
- "type": "image/png",
14
- "purpose": "any"
15
- },
16
- {
17
- "src": "/android-chrome-512x512.png",
18
- "sizes": "512x512",
19
- "type": "image/png",
20
- "purpose": "any"
21
- }
22
- ]
23
- }
package/public/style.css DELETED
@@ -1,261 +0,0 @@
1
- :root {
2
- --bg: #f7f7f5;
3
- --surface: #ffffff;
4
- --surface-subtle: #f1f1ee;
5
- --ink: #171719;
6
- --muted: #68686f;
7
- --line: #e1e1dc;
8
- --line-strong: #cfcfc8;
9
- --accent: #4c50d8;
10
- --accent-hover: #3f43c3;
11
- --accent-soft: #f0f0ff;
12
- --danger: #b42318;
13
- --danger-soft: #fff2f0;
14
- --ok: #16875b;
15
- --ok-soft: #edf8f3;
16
- --radius: 12px;
17
- --container: 1180px;
18
- color: var(--ink);
19
- background: var(--bg);
20
- font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
21
- font-synthesis: none;
22
- text-rendering: optimizeLegibility;
23
- }
24
-
25
- * { box-sizing: border-box; }
26
- html { scroll-behavior: smooth; background: var(--bg); }
27
- body { margin: 0; min-width: 320px; background: var(--bg); color: var(--ink); line-height: 1.55; -webkit-font-smoothing: antialiased; }
28
- a { color: inherit; text-decoration: none; }
29
- button, input { font: inherit; }
30
- button, a { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
31
- button { cursor: pointer; }
32
- button:disabled { cursor: not-allowed; opacity: .58; }
33
- img, svg { display: block; }
34
- .hidden { display: none !important; }
35
-
36
- .skip-link { position: fixed; top: 12px; left: 12px; z-index: 100; transform: translateY(-160%); padding: 10px 14px; border-radius: 8px; background: var(--ink); color: #fff; font-weight: 650; transition: transform 140ms ease; }
37
- .skip-link:focus { transform: translateY(0); }
38
-
39
- .site-header { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 28px; padding: 0 max(20px, calc((100vw - var(--container)) / 2)); border-bottom: 1px solid var(--line); background: rgba(247, 247, 245, .94); }
40
- .wordmark { display: inline-flex; align-items: center; gap: 9px; min-width: 0; font-size: 15px; font-weight: 760; letter-spacing: -.025em; }
41
- .wordmark span[translate="no"] { white-space: nowrap; }
42
- .brand-mark { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 8px; }
43
- .site-header nav, .site-footer nav { display: flex; align-items: center; gap: 22px; }
44
- .site-header nav a, .site-footer nav a { color: var(--muted); font-size: 14px; font-weight: 550; }
45
- .site-header nav a:hover, .site-footer nav a:hover, .site-header nav a[aria-current="page"] { color: var(--ink); }
46
- .site-header nav a[aria-current="page"] { font-weight: 680; }
47
-
48
- main { display: block; }
49
- .hero { max-width: var(--container); min-height: 560px; margin: 0 auto; padding: clamp(64px, 8vw, 92px) 20px; display: grid; grid-template-columns: minmax(0, 1.03fr) minmax(360px, .97fr); align-items: center; gap: clamp(48px, 7vw, 96px); }
50
- .hero-copy { max-width: 650px; }
51
- .eyebrow { margin: 0 0 16px; color: var(--accent); font-size: 12px; font-weight: 760; letter-spacing: .13em; text-transform: uppercase; }
52
- .hero h1, .section-heading h2, .final-cta h2, .prose h1 { text-wrap: balance; }
53
- .hero h1 { margin: 0; max-width: 680px; font-size: clamp(52px, 6.2vw, 82px); line-height: .98; letter-spacing: -.06em; }
54
- .lede { max-width: 610px; margin: 25px 0 0; color: var(--muted); font-size: clamp(18px, 1.8vw, 21px); line-height: 1.58; text-wrap: pretty; }
55
- .hero-actions { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-top: 30px; }
56
-
57
- .button { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; border: 1px solid var(--ink); border-radius: 9px; background: var(--ink); color: #fff; font-weight: 680; line-height: 1.2; transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease; }
58
- .button:hover { background: #29292d; border-color: #29292d; }
59
- .button:active { transform: translateY(1px); }
60
- .button.secondary { border-color: var(--line-strong); background: var(--surface); color: var(--ink); }
61
- .button.secondary:hover { border-color: #b8b8b1; background: var(--surface-subtle); }
62
- .button.ghost { border-color: transparent; background: transparent; color: var(--muted); }
63
- .button.ghost:hover { border-color: var(--line); background: var(--surface-subtle); color: var(--ink); }
64
- .text-link { display: inline-flex; align-items: center; gap: 6px; padding: 7px 0; color: var(--ink); font-weight: 650; border-bottom: 1px solid var(--line-strong); }
65
- .text-link:hover { border-color: var(--ink); }
66
-
67
- .provider-stack { width: min(100%, 400px); display: grid; gap: 10px; margin-top: 4px; }
68
- .provider-button { position: relative; width: 100%; min-height: 46px; display: flex; align-items: center; justify-content: center; padding: 10px 16px 10px 48px; border-radius: 7px; font-size: 14px; font-weight: 600; line-height: 1; text-align: center; box-shadow: none; }
69
- .provider-button:active { transform: none; }
70
- .provider-icon { position: absolute; left: 14px; top: 50%; width: 20px; height: 20px; transform: translateY(-50%); }
71
- .google-button { border: 1px solid #747775; background: #fff; color: #1f1f1f; font-family: Roboto, Arial, sans-serif; font-weight: 500; }
72
- .google-button:hover { border-color: #5f6368; background: #f8fafd; color: #1f1f1f; }
73
- .github-button { border: 1px solid #1f2328; background: #24292f; color: #fff; }
74
- .github-button:hover { border-color: #1f2328; background: #1f2328; color: #fff; }
75
-
76
- .hero-visual { width: 100%; max-width: 520px; margin-left: auto; }
77
- .node { min-height: 62px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 17px 18px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
78
- .node span { font-weight: 690; }
79
- .node b { color: var(--muted); font-size: 12px; font-weight: 600; }
80
- .node.relay { border-color: #aeb0f4; background: #fbfbff; }
81
- .node.device-node { position: relative; padding-right: 94px; }
82
- .node .pulse { position: absolute; right: 16px; padding: 4px 8px; border-radius: 999px; background: var(--ok-soft); color: var(--ok); font-size: 11px; font-style: normal; font-weight: 650; }
83
- .rail { position: relative; height: 58px; display: grid; place-items: center; }
84
- .rail span { position: absolute; inset: 0 auto; width: 1px; background: var(--line-strong); }
85
- .rail i { position: relative; padding: 3px 8px; background: var(--bg); color: var(--muted); font-size: 11px; font-style: normal; }
86
-
87
- .principles { max-width: var(--container); margin: 0 auto; padding: 0 20px 72px; display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line); }
88
- .principles > div { min-height: 210px; padding: 36px 34px 0 0; }
89
- .principles > div + div { padding-left: 34px; border-left: 1px solid var(--line); }
90
- .principles span { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
91
- .principles h2 { margin: 28px 0 9px; font-size: 21px; line-height: 1.2; letter-spacing: -.025em; }
92
- .principles p { max-width: 330px; margin: 0; color: var(--muted); font-size: 15px; }
93
-
94
-
95
- .quickstart { max-width: var(--container); margin: 0 auto; padding: clamp(72px, 9vw, 104px) 20px; border-top: 1px solid var(--line); }
96
- .quickstart .section-heading { margin-left: 0; margin-right: 0; }
97
- .quickstart-grid { display: grid; grid-template-columns: 1.35fr .8fr .8fr; gap: 0; margin: 34px 0 26px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
98
- .command-card { min-width: 0; padding: 28px 28px 30px 0; }
99
- .command-card + .command-card { padding-left: 28px; border-left: 1px solid var(--line); }
100
- .command-card > span { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
101
- .command-card h3 { margin: 18px 0 8px; font-size: 20px; letter-spacing: -.025em; }
102
- .command-card p { margin: 12px 0 0; color: var(--muted); font-size: 14px; }
103
- .site-header nav { min-width: 0; overflow-x: auto; scrollbar-width: none; }
104
- .site-header nav::-webkit-scrollbar { display: none; }
105
-
106
- .workspace { padding: clamp(72px, 9vw, 112px) 20px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--surface); }
107
- .workspace > * { max-width: var(--container); margin-left: auto; margin-right: auto; }
108
- .section-heading { max-width: 720px; margin-bottom: 45px; }
109
- .section-heading h2, .final-cta h2 { margin: 0; font-size: clamp(38px, 5vw, 58px); line-height: 1.02; letter-spacing: -.05em; }
110
- .section-heading > p:last-child, .final-cta > p { max-width: 620px; margin: 16px 0 0; color: var(--muted); font-size: 18px; }
111
- .workspace-grid { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
112
- .auth-pane, .endpoint-pane { min-width: 0; padding: 34px 0 38px; }
113
- .auth-pane { padding-right: 48px; border-right: 1px solid var(--line); }
114
- .endpoint-pane { padding-left: 48px; }
115
- .auth-pane h3, .endpoint-pane h3, .pair-panel h3, .devices-panel h3, .oauth-card h2 { margin: 0 0 9px; font-size: 20px; line-height: 1.25; letter-spacing: -.025em; }
116
- .stack { display: flex; flex-direction: column; gap: 10px; }
117
- .muted { color: var(--muted); }
118
- .error { margin: 2px 0 0; color: var(--danger); font-size: 13px; }
119
- p:empty, .error:empty, #status:empty { display: none; }
120
- .identity { display: flex; align-items: center; gap: 11px; }
121
- .identity small { display: block; color: var(--muted); }
122
- .status-dot { width: 9px; height: 9px; flex: 0 0 9px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); }
123
-
124
- .copy-field { position: relative; min-width: 0; display: flex; align-items: center; margin-top: 14px; border: 1px solid var(--line-strong); border-radius: 9px; background: var(--surface); overflow: hidden; }
125
- .copy-field code { min-width: 0; flex: 1; overflow-x: auto; padding: 12px 48px 12px 13px; color: #2e2e33; font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre; scrollbar-width: thin; }
126
- .copy-field pre { min-width: 0; flex: 1; margin: 0; overflow: auto; }
127
- .copy-field pre code { display: block; }
128
- .copy-field.multiline { align-items: stretch; }
129
- .copy-field.compact { width: min(100%, 300px); margin-top: 0; }
130
- .copy-icon-button { position: absolute; top: 50%; right: 6px; width: 34px; height: 34px; display: grid; place-items: center; padding: 0; transform: translateY(-50%); border: 0; border-radius: 7px; background: transparent; color: #65656b; }
131
- .copy-icon-button:hover { background: var(--surface-subtle); color: var(--ink); }
132
- .copy-icon-button svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
133
- .copy-icon-button .icon-check { display: none; }
134
- .copy-icon-button[data-copied="true"] { color: var(--ok); }
135
- .copy-icon-button[data-copied="true"] .icon-copy { display: none; }
136
- .copy-icon-button[data-copied="true"] .icon-check { display: block; }
137
- .pairing-output { grid-column: 1 / -1; margin-top: 4px; }
138
- .pairing-output .copy-field { width: 100%; }
139
- .pairing-output p { margin: 8px 0 0; }
140
- .update-line { display: flex; align-items: center; gap: 14px; margin: 20px 0 24px; color: var(--muted); font-size: 13px; }
141
- .endpoint-facts { margin: 28px 0 0; border-top: 1px solid var(--line); }
142
- .endpoint-facts div { display: flex; justify-content: space-between; gap: 20px; padding: 12px 0; border-bottom: 1px solid var(--line); }
143
- .endpoint-facts dt { color: var(--muted); }
144
- .endpoint-facts dd { margin: 0; text-align: right; font-weight: 650; }
145
- .device-workspace { margin-top: 52px; }
146
- .pair-panel { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px; align-items: center; padding-bottom: 34px; border-bottom: 1px solid var(--line); }
147
- .devices-panel { padding-top: 34px; }
148
- .panel-title { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; }
149
- .device-list { margin-top: 14px; border-top: 1px solid var(--line); }
150
- .device { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 18px; padding: 17px 0; border-bottom: 1px solid var(--line); }
151
- .device strong { font-size: 15px; }
152
- .device .muted { overflow-wrap: anywhere; font-size: 13px; }
153
- .online, .offline { display: inline-flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 12px; }
154
- .online { color: var(--ok); }
155
- .offline { color: var(--muted); }
156
- .danger { min-height: 38px; padding: 8px 11px; border: 1px solid #e7c3be; border-radius: 8px; background: transparent; color: var(--danger); font-weight: 650; }
157
- .danger:hover { background: var(--danger-soft); }
158
-
159
- .final-cta { max-width: var(--container); margin: 0 auto; padding: clamp(72px, 9vw, 112px) 20px; }
160
- .final-cta .text-link { margin-top: 24px; }
161
- .site-footer { max-width: var(--container); min-height: 86px; margin: 0 auto; padding: 22px 20px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 26px; border-top: 1px solid var(--line); font-size: 13px; }
162
- .site-footer p { margin: 0; color: var(--muted); text-align: right; }
163
-
164
- .doc-shell { width: min(100%, var(--container)); margin: 0 auto; padding: 48px 20px 76px; display: grid; grid-template-columns: 176px minmax(0, 760px); gap: clamp(36px, 6vw, 72px); }
165
- .doc-nav { position: sticky; top: 28px; align-self: start; display: flex; flex-direction: column; gap: 4px; }
166
- .doc-nav a { padding: 8px 10px; border-radius: 7px; color: var(--muted); font-size: 14px; font-weight: 550; }
167
- .doc-nav a:hover { background: var(--surface-subtle); color: var(--ink); }
168
- .doc-nav a[aria-current="page"] { background: var(--surface-subtle); color: var(--ink); font-weight: 680; }
169
- .prose { min-width: 0; }
170
- .prose h1 { margin: 0 0 18px; max-width: 700px; font-size: clamp(38px, 5vw, 54px); line-height: 1.04; letter-spacing: -.045em; }
171
- .prose h2 { scroll-margin-top: 24px; margin: 36px 0 9px; font-size: 23px; line-height: 1.22; letter-spacing: -.03em; }
172
- .prose h3 { scroll-margin-top: 24px; margin: 24px 0 7px; font-size: 17px; }
173
- .prose p, .prose li { color: #505057; font-size: 15.5px; text-wrap: pretty; }
174
- .prose p { margin: 0 0 12px; }
175
- .prose a { color: #3538ae; text-decoration: underline; text-decoration-color: #a5a7e9; text-underline-offset: 3px; }
176
- .prose a:hover { text-decoration-color: #3538ae; }
177
- .prose pre { margin: 0; }
178
- .prose .copy-field { margin: 12px 0 16px; background: #fbfbfa; }
179
- .prose .copy-field code { font-size: 12.5px; }
180
- .prose code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .9em; }
181
- .prose :not(pre) > code { padding: 2px 5px; border-radius: 5px; background: var(--surface-subtle); color: #34343a; }
182
- .prose hr { margin: 46px 0; border: 0; border-top: 1px solid var(--line); }
183
- .prose .notice { margin: 20px 0; padding: 13px 15px; border: 1px solid #d8d8f7; border-radius: 9px; background: var(--accent-soft); }
184
- .prose .notice p { margin: 5px 0 0; }
185
-
186
- .oauth-shell { min-height: calc(100svh - 68px); display: grid; place-items: center; padding: 56px 20px 80px; }
187
- .oauth-card { width: min(100%, 520px); padding: 30px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); }
188
- .oauth-card .eyebrow { margin-bottom: 11px; }
189
- .oauth-card > p:first-child { margin-top: 0; }
190
- .oauth-card .provider-stack { width: 100%; margin-top: 20px; }
191
- .scope-list { margin: 24px 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
192
- .scope-list li { display: flex; justify-content: space-between; gap: 20px; padding: 13px 0; border-bottom: 1px solid var(--line); }
193
- .scope-list strong { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 600; }
194
- .oauth-actions { display: flex; gap: 9px; margin-top: 18px; }
195
- .oauth-meta { overflow-wrap: anywhere; color: var(--muted); font-size: 12px; }
196
- .footer-mini { margin: 26px 0 0; color: var(--muted); font-size: 12px; }
197
- .footer-mini a { text-decoration: underline; text-underline-offset: 3px; }
198
-
199
- input:focus-visible, button:focus-visible, a:focus-visible { outline: 3px solid rgba(76, 80, 216, .28); outline-offset: 2px; }
200
-
201
- @media (max-width: 900px) {
202
- .hero { min-height: auto; grid-template-columns: 1fr; padding-top: 76px; padding-bottom: 76px; }
203
- .hero-copy { max-width: 700px; }
204
- .hero-visual { max-width: 680px; margin: 4px 0 0; }
205
- .principles { grid-template-columns: 1fr; padding-bottom: 58px; }
206
- .principles > div, .principles > div + div { min-height: auto; padding: 28px 0; border-left: 0; border-bottom: 1px solid var(--line); }
207
- .principles > div:last-child { border-bottom: 0; }
208
- .workspace-grid { grid-template-columns: 1fr; }
209
- .quickstart-grid { grid-template-columns: 1fr; }
210
- .command-card, .command-card + .command-card { padding: 24px 0; border-left: 0; border-bottom: 1px solid var(--line); }
211
- .command-card:last-child { border-bottom: 0; }
212
- .auth-pane { padding-right: 0; border-right: 0; border-bottom: 1px solid var(--line); }
213
- .endpoint-pane { padding-left: 0; }
214
- .doc-shell { grid-template-columns: 1fr; gap: 0; padding-top: 34px; }
215
- .doc-nav { display: none; }
216
- .site-footer { grid-template-columns: 1fr; }
217
- .site-footer nav { flex-wrap: wrap; }
218
- .site-footer p { text-align: left; }
219
- }
220
-
221
- @media (max-width: 620px) {
222
- .site-header { min-height: 62px; padding: 0 16px; }
223
- .site-header { gap: 12px; }
224
- .site-header nav { max-width: calc(100vw - 116px); gap: 12px; }
225
- .site-header nav a { font-size: 12.5px; }
226
- .hero { padding: 58px 18px 64px; }
227
- .hero h1 { font-size: clamp(46px, 14vw, 60px); }
228
- .hero-actions { align-items: stretch; flex-direction: column; gap: 10px; }
229
- .hero-actions .button, .hero-actions .text-link { width: 100%; justify-content: center; }
230
- .node { align-items: flex-start; flex-direction: column; gap: 3px; }
231
- .node.device-node { padding-right: 18px; }
232
- .node .pulse { top: 15px; }
233
- .workspace, .final-cta, .quickstart { padding: 50px 18px; }
234
- .section-heading { margin-bottom: 30px; }
235
- .section-heading h2, .final-cta h2 { font-size: 36px; }
236
- .section-heading > p:last-child, .final-cta > p { margin-top: 12px; font-size: 16px; }
237
- .oauth-actions { flex-direction: column; }
238
- .oauth-actions .button { width: 100%; }
239
- .update-line { align-items: stretch; flex-direction: column; gap: 8px; }
240
- .copy-field.compact { width: 100%; }
241
- .pair-panel { grid-template-columns: 1fr; }
242
- .pair-panel .button { justify-self: start; }
243
- .panel-title { align-items: flex-start; flex-direction: column; }
244
- .endpoint-facts div { align-items: flex-start; flex-direction: column; gap: 2px; }
245
- .endpoint-facts dd { text-align: left; }
246
- .site-footer { padding: 24px 18px; }
247
- .site-footer nav { gap: 14px; }
248
- .doc-shell { gap: 18px; padding: 24px 16px 56px; }
249
- .prose h1 { margin-bottom: 14px; font-size: clamp(34px, 10vw, 42px); }
250
- .prose h2 { margin-top: 28px; font-size: 21px; }
251
- .prose h3 { margin-top: 21px; }
252
- .prose p, .prose li { font-size: 15px; }
253
- .oauth-shell { min-height: calc(100svh - 62px); padding: 28px 14px 60px; place-items: start center; }
254
- .oauth-card { padding: 22px 18px; }
255
- .scope-list li { align-items: flex-start; flex-direction: column; gap: 4px; }
256
- }
257
-
258
- @media (prefers-reduced-motion: reduce) {
259
- html { scroll-behavior: auto; }
260
- *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
261
- }
@@ -1,75 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="theme-color" content="#f7f7f5">
8
- <title>Support · ReMCP</title>
9
- <meta name="description" content="Troubleshoot ReMCP sign-in, device pairing, agent connectivity and MCP authorization.">
10
- <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-013">
12
- <meta property="og:site_name" content="ReMCP">
13
- <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
- <link rel="icon" href="/favicon.ico" sizes="any">
15
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" media="(prefers-color-scheme: light)">
16
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" media="(prefers-color-scheme: light)">
17
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" media="(prefers-color-scheme: light)">
18
- <link rel="icon" type="image/png" sizes="16x16" href="/remcp-darkmode-16x16.png" media="(prefers-color-scheme: dark)">
19
- <link rel="icon" type="image/png" sizes="32x32" href="/remcp-darkmode-32x32.png" media="(prefers-color-scheme: dark)">
20
- <link rel="icon" type="image/png" sizes="48x48" href="/remcp-darkmode-48x48.png" media="(prefers-color-scheme: dark)">
21
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
22
- <link rel="manifest" href="/site.webmanifest">
23
- </head>
24
- <body>
25
- <a class="skip-link" href="#main">Skip to content</a>
26
- <header class="site-header">
27
- <a class="wordmark" href="/" aria-label="ReMCP home">
28
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
- <span translate="no">ReMCP</span>
30
- </a>
31
- <nav aria-label="Primary"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support" aria-current="page">Support</a></nav>
32
- </header>
33
- <main id="main" class="doc-shell">
34
- <aside class="doc-nav" aria-label="Documentation"><a href="/docs">Setup</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support" aria-current="page">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></aside>
35
- <article class="prose">
36
- <p class="eyebrow">Support</p>
37
- <h1>Troubleshooting.</h1>
38
- <p>Check the layer that is failing: sign-in, pairing, the local agent, or MCP authorization.</p>
39
-
40
- <h2>Sign-in does not open</h2>
41
- <p>Use <strong>Continue with Google</strong> or <strong>Continue with GitHub</strong>. ReMCP does not use email/password sign-in on the hosted site. If a popup is blocked, allow popups for <code>remcp.delio24.com</code> and try again.</p>
42
-
43
- <h2>The <code>remcp</code> command is missing</h2>
44
- <div class="copy-field"><code id="support-install">npm install --global @remcp/remcp@latest</code><button class="copy-icon-button" type="button" data-copy="#support-install" aria-label="Copy install command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
45
- <p>After installation, pair the computer from the signed-in workspace. The public install command alone does not pair a device.</p>
46
-
47
- <h2>Pairing command expired</h2>
48
- <p>Open the workspace and generate a new command. Each pairing command contains a one-time code and expires automatically.</p>
49
-
50
- <h2>Device shows offline</h2>
51
- <div class="copy-field"><code id="support-status">remcp status</code><button class="copy-icon-button" type="button" data-copy="#support-status" aria-label="Copy status command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
52
- <p>Confirm the user service is running and outbound HTTPS/WSS access to <code>remcp.delio24.com</code> is allowed. Re-pair only if the device credential was revoked or the local pairing state was removed.</p>
53
-
54
- <h2>OAuth does not complete</h2>
55
- <p>Return to the authorization page, sign in with the intended ReMCP account, review the requested scopes, and choose <strong>Authorize ReMCP Access</strong>.</p>
56
-
57
- <h2>MCP tool cannot run</h2>
58
- <p>Confirm the target device is online. A device tool needs the current ReMCP device id; clients can obtain it with <code>list_devices</code>.</p>
59
-
60
- <h2>Update or repair</h2>
61
- <div class="copy-field"><code id="support-update">remcp update</code><button class="copy-icon-button" type="button" data-copy="#support-update" aria-label="Copy update command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
62
- <p>If the CLI is paired but its background service is missing, run <code>remcp install</code>.</p>
63
- </article>
64
- </main>
65
- <footer class="site-footer">
66
- <div class="wordmark">
67
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
68
- <span translate="no">ReMCP</span>
69
- </div>
70
- <nav aria-label="Footer"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></nav>
71
- <p>Open-source remote MCP infrastructure.</p>
72
- </footer>
73
- <script type="module" src="/copy.js?v=remcp-013"></script>
74
- </body>
75
- </html>
package/public/terms.html DELETED
@@ -1,77 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="theme-color" content="#f7f7f5">
8
- <title>Terms of Service · ReMCP</title>
9
- <meta name="description" content="Terms for using the hosted ReMCP service and its remote-computer capabilities.">
10
- <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-013">
12
- <meta property="og:site_name" content="ReMCP">
13
- <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
- <link rel="icon" href="/favicon.ico" sizes="any">
15
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" media="(prefers-color-scheme: light)">
16
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" media="(prefers-color-scheme: light)">
17
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" media="(prefers-color-scheme: light)">
18
- <link rel="icon" type="image/png" sizes="16x16" href="/remcp-darkmode-16x16.png" media="(prefers-color-scheme: dark)">
19
- <link rel="icon" type="image/png" sizes="32x32" href="/remcp-darkmode-32x32.png" media="(prefers-color-scheme: dark)">
20
- <link rel="icon" type="image/png" sizes="48x48" href="/remcp-darkmode-48x48.png" media="(prefers-color-scheme: dark)">
21
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
22
- <link rel="manifest" href="/site.webmanifest">
23
- </head>
24
- <body>
25
- <a class="skip-link" href="#main">Skip to content</a>
26
- <header class="site-header">
27
- <a class="wordmark" href="/" aria-label="ReMCP home">
28
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
- <span translate="no">ReMCP</span>
30
- </a>
31
- <nav aria-label="Primary"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a></nav>
32
- </header>
33
- <main id="main" class="doc-shell">
34
- <aside class="doc-nav" aria-label="Documentation"><a href="/docs">Setup</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms" aria-current="page">Terms</a></aside>
35
- <article class="prose">
36
- <p class="eyebrow">Legal</p>
37
- <h1>Terms of Service</h1>
38
- <p><strong>Effective September 16, 2026.</strong> These Terms apply to the hosted ReMCP service at <code>remcp.delio24.com</code>. The open-source software can also be self-hosted under its software licenses.</p>
39
-
40
- <h2>Authorized systems only</h2>
41
- <p>You may pair and control only computers and accounts that you own or are authorized to administer. You are responsible for protecting your sign-in account and revoking devices or OAuth grants you no longer trust.</p>
42
-
43
- <h2>Remote actions</h2>
44
- <p>ReMCP can read and modify files and can start, interact with or terminate processes. Some commands can affect external systems. Review consequential actions and maintain appropriate backups and operating-system permissions.</p>
45
-
46
- <h2>Acceptable use</h2>
47
- <p>Do not use ReMCP to access systems without authorization, steal credentials, distribute malware, evade security controls, interfere with other users, or violate applicable law or third-party rights.</p>
48
-
49
- <h2>Sign-in providers</h2>
50
- <p>The hosted service uses Firebase Authentication with Google and GitHub sign-in. Those providers and other infrastructure dependencies are governed by their own terms. ReMCP does not grant rights to third-party systems accessible from a paired computer.</p>
51
-
52
- <h2>Service operation</h2>
53
- <p>The hosted service may be updated, rate-limited, suspended or discontinued for security, abuse prevention, maintenance or legal reasons. Uninterrupted availability is not guaranteed. Self-hosted deployments are operated and secured by their administrators.</p>
54
-
55
- <h2>Open-source software</h2>
56
- <p>ReMCP and its dependencies include open-source software. Their license notices govern those software components independently of these hosted-service Terms.</p>
57
-
58
- <h2>No warranty</h2>
59
- <p>To the maximum extent permitted by applicable law, the service and software are provided “as is” and “as available,” without warranties of uninterrupted operation or fitness for a particular purpose. Rights that cannot legally be excluded remain unaffected.</p>
60
-
61
- <h2>Responsibility for actions</h2>
62
- <p>You are responsible for commands and changes made through devices you authorize. To the maximum extent permitted by law, ReMCP is not responsible for indirect or consequential loss arising from device changes, lost data, third-party systems or service interruption.</p>
63
-
64
- <h2>Termination and changes</h2>
65
- <p>You may stop using the service and revoke devices at any time. Access may be suspended for security, abuse or legal reasons. Material changes to these Terms will be published at this URL with a new effective date.</p>
66
- </article>
67
- </main>
68
- <footer class="site-footer">
69
- <div class="wordmark">
70
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
71
- <span translate="no">ReMCP</span>
72
- </div>
73
- <nav aria-label="Footer"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></nav>
74
- <p>Open-source remote MCP infrastructure.</p>
75
- </footer>
76
- </body>
77
- </html>
@@ -1,36 +0,0 @@
1
- ---
2
- name: remcp-operator
3
- description: Safely operate computers paired through ReMCP. Use when the user asks to inspect or change files, directories, local processes, terminal sessions, or ReMCP device state on one of their paired computers.
4
- ---
5
-
6
- # ReMCP Operator
7
-
8
- Use ReMCP only when the request actually needs a paired computer. Do not invoke ReMCP for general knowledge, writing, weather, web research, or conceptual questions that can be answered without the user's device.
9
-
10
- ## Device selection
11
-
12
- 1. Call `list_devices` before the first device operation unless a current ReMCP device id is already unambiguous in the conversation.
13
- 2. Prefer an online device whose user-facing name or hostname matches the request.
14
- 3. If more than one online device plausibly matches and choosing the wrong machine could change state, ask the user which device to use.
15
-
16
- ## Read before write
17
-
18
- Inspect the smallest amount of device state needed to understand the request before changing it. Prefer `list_directory`, `get_file_info`, `read_file`, `list_processes`, or existing process output before a write or terminal mutation.
19
- ## Changes
20
-
21
- - Use the narrowest tool that performs the requested change. Prefer `edit_block`, `write_file`, or `move_file` over a shell command when the dedicated tool expresses the action clearly.
22
- - Do not broaden a requested path, command, or target without a reason tied to the user's task.
23
- - Treat `set_config_value`, file replacement, process termination, and generic terminal commands as state-changing actions.
24
- - Generic terminal commands may also affect external services. Keep commands scoped to the user's stated goal and surface consequential effects before execution when they are not already clear from the request.
25
-
26
- ## Verification
27
-
28
- After a change, use the cheapest relevant read to verify the result. Examples: re-read the edited range, list the destination directory after a move, or read process output after starting a command.
29
-
30
- ## Long-running processes
31
-
32
- Use `start_process` once and then `read_process_output` or `interact_with_process` for the existing session. Avoid starting duplicate long-running processes just to obtain new output.
33
-
34
- ## Out-of-scope requests
35
-
36
- Do not use ReMCP to access a computer the user has not paired or is not authorized to control. Do not ask for or process passwords, MFA codes, private keys, API keys, payment-card data, protected health information, government identifiers, or other restricted credentials/data through ReMCP tools. If a requested file or command would expose those categories, ask the user to use a safer local workflow instead.
package/src/auth.mjs DELETED
@@ -1,71 +0,0 @@
1
- import { createRemoteJWKSet, jwtVerify, SignJWT } from 'jose';
2
- import { config } from './config.mjs';
3
- import { bearer } from './util.mjs';
4
- import { upsertUser } from './db.mjs';
5
-
6
- const firebaseJwks = createRemoteJWKSet(new URL('https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com'));
7
- const signingKey = new TextEncoder().encode(config.tokenSecret);
8
-
9
- export async function verifyFirebaseToken(token) {
10
- const { payload } = await jwtVerify(token, firebaseJwks, {
11
- issuer: `https://securetoken.google.com/${config.firebaseProjectId}`,
12
- audience: config.firebaseProjectId,
13
- });
14
- if (!payload.sub) throw new Error('Firebase token has no subject');
15
- const email = typeof payload.email === 'string' ? payload.email.toLowerCase() : '';
16
- const uid = config.ownerEmail && email === config.ownerEmail ? `owner:${config.ownerEmail}` : payload.sub;
17
- return {
18
- uid,
19
- firebaseUid: payload.sub,
20
- email,
21
- emailVerified: payload.email_verified === true,
22
- name: typeof payload.name === 'string' ? payload.name : '',
23
- };
24
- }
25
-
26
- export async function requireFirebase(req, res, next) {
27
- try {
28
- const token = bearer(req);
29
- if (!token) return res.status(401).json({ error: 'firebase_auth_required' });
30
- const user = await verifyFirebaseToken(token);
31
- upsertUser(user);
32
- req.user = user;
33
- next();
34
- } catch (error) {
35
- res.status(401).json({ error: 'invalid_firebase_token', message: error.message });
36
- }
37
- }
38
-
39
- export async function signMcpAccessToken({ uid, clientId, scope }) {
40
- return new SignJWT({ scope, client_id: clientId, typ: 'remcp_access' })
41
- .setProtectedHeader({ alg: 'HS256' })
42
- .setIssuer(config.publicUrl)
43
- .setAudience(`${config.publicUrl}/mcp`)
44
- .setSubject(uid)
45
- .setIssuedAt()
46
- .setExpirationTime(`${config.accessTtlSeconds}s`)
47
- .sign(signingKey);
48
- }
49
-
50
- export async function verifyMcpAccessToken(token) {
51
- const { payload } = await jwtVerify(token, signingKey, {
52
- issuer: config.publicUrl,
53
- audience: `${config.publicUrl}/mcp`,
54
- });
55
- if (payload.typ !== 'remcp_access' || !payload.sub) throw new Error('Invalid ReMCP access token');
56
- return { uid: payload.sub, scope: String(payload.scope || ''), clientId: String(payload.client_id || '') };
57
- }
58
-
59
- export async function requireMcpAuth(req, res, next) {
60
- try {
61
- const token = bearer(req);
62
- if (!token) throw new Error('missing_token');
63
- const auth = await verifyMcpAccessToken(token);
64
- if (!auth.scope.split(/\s+/).includes('remcp:control')) throw new Error('missing_scope');
65
- req.mcpAuth = auth;
66
- next();
67
- } catch {
68
- res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${config.publicUrl}/.well-known/oauth-protected-resource/mcp"`);
69
- res.status(401).json({ error: 'unauthorized' });
70
- }
71
- }
package/src/config.mjs DELETED
@@ -1,51 +0,0 @@
1
- import process from 'node:process';
2
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { randomBytes } from 'node:crypto';
5
-
6
- function required(name, fallback = '') {
7
- const value = process.env[name] || fallback;
8
- if (!value) throw new Error(`${name} is required`);
9
- return value;
10
- }
11
-
12
- const publicUrl = required('PUBLIC_URL', 'http://127.0.0.1:8787').replace(/\/$/, '');
13
- const dataDir = process.env.DATA_DIR || './data';
14
- function loadSigningSecret() {
15
- const configured = process.env.TOKEN_SECRET || '';
16
- if (configured) { if (configured.length < 32) throw new Error('TOKEN_SECRET must be at least 32 characters'); return configured; }
17
- mkdirSync(dataDir, { recursive: true });
18
- const file = join(dataDir, 'token-secret');
19
- try { return readFileSync(file, 'utf8').trim(); } catch {}
20
- const generated = randomBytes(48).toString('base64url');
21
- writeFileSync(file, generated + '\n', { mode: 0o600 });
22
- return generated;
23
- }
24
- const tokenSecret = loadSigningSecret();
25
- const installSpec = (process.env.REMCP_INSTALL_SPEC || '@remcp/remcp@latest').trim();
26
- if (!/^[A-Za-z0-9@._:/#-]+$/.test(installSpec)) throw new Error('REMCP_INSTALL_SPEC contains unsupported characters');
27
-
28
- export const config = Object.freeze({
29
- publicUrl,
30
- port: Number(process.env.PORT || 8787),
31
- dataDir,
32
- tokenSecret,
33
- installSpec,
34
- firebaseProjectId: required('FIREBASE_PROJECT_ID', 'remcp-33037'),
35
- ownerEmail: (process.env.OWNER_EMAIL || '').trim().toLowerCase(),
36
- pairTtlSeconds: Number(process.env.PAIR_TTL_SECONDS || 600),
37
- accessTtlSeconds: Number(process.env.ACCESS_TTL_SECONDS || 3600),
38
- refreshTtlSeconds: Number(process.env.REFRESH_TTL_SECONDS || 2592000),
39
- rpcTimeoutMs: Number(process.env.RPC_TIMEOUT_MS || 120000),
40
- domainVerificationToken: (process.env.OPENAI_DOMAIN_VERIFICATION_TOKEN || '').trim(),
41
- domainVerificationPath: (process.env.OPENAI_DOMAIN_VERIFICATION_PATH || '/.well-known/openai-apps-challenge').trim(),
42
- firebase: {
43
- apiKey: required('FIREBASE_API_KEY'),
44
- authDomain: required('FIREBASE_AUTH_DOMAIN'),
45
- projectId: required('FIREBASE_PROJECT_ID', 'remcp-33037'),
46
- storageBucket: process.env.FIREBASE_STORAGE_BUCKET || '',
47
- messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID || '',
48
- appId: required('FIREBASE_APP_ID'),
49
- measurementId: process.env.FIREBASE_MEASUREMENT_ID || '',
50
- },
51
- });