@rotifer/mcp-server 0.19.7 → 0.20.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/README.md +95 -8
- package/dist/auth.js +8 -35
- package/dist/auth.js.map +1 -1
- package/dist/callback-page.d.ts +1 -0
- package/dist/callback-page.js +42 -0
- package/dist/callback-page.js.map +1 -0
- package/dist/http.d.ts +28 -4
- package/dist/http.js +135 -8
- package/dist/http.js.map +1 -1
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/local.d.ts +1 -1
- package/dist/local.js +1 -1
- package/dist/login.d.ts +3 -0
- package/dist/login.js +37 -34
- package/dist/login.js.map +1 -1
- package/dist/open-browser.d.ts +13 -2
- package/dist/open-browser.js +18 -4
- package/dist/open-browser.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +7 -4
- package/dist/server.js.map +1 -1
- package/dist/tool-sets.d.ts +8 -0
- package/dist/tool-sets.js +13 -0
- package/dist/tool-sets.js.map +1 -1
- package/dist/tools.d.ts +1 -2
- package/dist/tools.js +31 -27
- package/dist/tools.js.map +1 -1
- package/dist/validate-url.d.ts +19 -0
- package/dist/validate-url.js +52 -0
- package/dist/validate-url.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -142,8 +142,8 @@ AI: → wrap_gene({ gene_name: "my-search", domain: "search.web", fidelity: "Wr
|
|
|
142
142
|
| Tool | Description | Key Parameters |
|
|
143
143
|
|------|-------------|----------------|
|
|
144
144
|
| `auth_status` | Check login status | — |
|
|
145
|
-
| `login` | OAuth login (GitHub/GitLab) | `provider
|
|
146
|
-
| `logout` | Clear credentials | — |
|
|
145
|
+
| `login` | OAuth login (GitHub/GitLab). Not available over HTTP | `provider` |
|
|
146
|
+
| `logout` | Clear credentials. Not available over HTTP | — |
|
|
147
147
|
| `get_mcp_stats` | MCP call analytics | `days` |
|
|
148
148
|
| `get_my_reputation` | Current user's reputation | — |
|
|
149
149
|
|
|
@@ -239,6 +239,8 @@ ROTIFER_CLOUD_ENDPOINT=https://your-instance.supabase.co
|
|
|
239
239
|
ROTIFER_CLOUD_ANON_KEY=your-anon-key
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
The `login` MCP tool always signs in to the configured endpoint and refuses an `endpoint` argument: an assistant can be steered by what it reads, and sign-in sends credentials to that address. To sign in to another instance yourself, run `rotifer-mcp-server login --endpoint <url>`, which accepts only `http://` or `https://` addresses.
|
|
243
|
+
|
|
242
244
|
### Choosing which tools to expose
|
|
243
245
|
|
|
244
246
|
All thirty-one tools are available by default. `ROTIFER_MCP_TOOLS` narrows that
|
|
@@ -352,14 +354,50 @@ it over Streamable HTTP instead:
|
|
|
352
354
|
```bash
|
|
353
355
|
rotifer-mcp-server serve # http://127.0.0.1:3000/mcp
|
|
354
356
|
rotifer-mcp-server serve --port 8080
|
|
355
|
-
|
|
357
|
+
|
|
358
|
+
# Reachable from other machines: needs a list of names and an access token
|
|
359
|
+
openssl rand -hex 32 > mcp-token
|
|
360
|
+
chmod 600 mcp-token
|
|
361
|
+
rotifer-mcp-server serve --host 0.0.0.0 --allowed-hosts mcp.example.com --token-file mcp-token
|
|
356
362
|
```
|
|
357
363
|
|
|
358
|
-
- **It listens on `127.0.0.1` unless you pass `--host`.**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
364
|
+
- **It listens on `127.0.0.1` unless you pass `--host`.** Its tools act with your
|
|
365
|
+
signed-in Rotifer account, so it is not reachable from other machines by
|
|
366
|
+
default. If you pass a non-loopback `--host`, the server says so on stderr when
|
|
367
|
+
it starts. Earlier versions listened on every interface.
|
|
368
|
+
- **A non-loopback `--host` needs `--allowed-hosts`.** List the host names or IP
|
|
369
|
+
addresses clients will use, separated by commas, with no scheme, port, path or
|
|
370
|
+
wildcard. Without the list, `serve` exits with status 2. Earlier versions
|
|
371
|
+
started anyway.
|
|
372
|
+
- **A non-loopback `--host` also needs an access token.** Pass `--token-file
|
|
373
|
+
<path>` or set `ROTIFER_MCP_HTTP_TOKEN`, not both. A line break at the end of
|
|
374
|
+
the file is ignored. The token is not accepted as a command-line value, because
|
|
375
|
+
other programs on the machine can read those. Without a token, `serve` exits
|
|
376
|
+
with status 2. On loopback a token is optional, and is enforced if you set one.
|
|
377
|
+
Earlier versions had no token.
|
|
378
|
+
- **Every path except `/health` only answers requests addressed to a name it
|
|
379
|
+
knows.** The `Host` header, and the `Origin` header when a browser sends one,
|
|
380
|
+
must be loopback or an entry in `--allowed-hosts`; anything else gets `403`.
|
|
381
|
+
Listening on loopback alone does not stop a web page from reaching the server
|
|
382
|
+
through DNS rebinding.
|
|
383
|
+
- **With a token set, every path except `/health` also needs `Authorization:
|
|
384
|
+
Bearer <token>`.** A missing or wrong token gets `401`. CORS preflight
|
|
385
|
+
(`OPTIONS`) is exempt, because browsers cannot attach the header to it; it
|
|
386
|
+
still has to pass the `Host` and `Origin` check, and it never reaches a tool.
|
|
387
|
+
- **`--allowed-hosts` and the token do different jobs, and a request must pass
|
|
388
|
+
both.** `--allowed-hosts` decides which names the server may be addressed by,
|
|
389
|
+
which stops DNS rebinding. It says nothing about who is calling. The token
|
|
390
|
+
decides who may call.
|
|
391
|
+
- **Everyone holding the token acts as the account signed in on the server.**
|
|
392
|
+
There are no per-user tokens or permission levels. To revoke access, change the
|
|
393
|
+
token and restart the server.
|
|
394
|
+
- **The server speaks plain HTTP.** The token travels with every request, so a
|
|
395
|
+
server reachable from other machines belongs behind an HTTPS reverse proxy or
|
|
396
|
+
tunnel.
|
|
397
|
+
- **`login` and `logout` are not available over HTTP.** They are not listed, and
|
|
398
|
+
calling them returns an error, because they act on the credentials of the
|
|
399
|
+
machine running the server. On that machine, run `rotifer-mcp-server login` or
|
|
400
|
+
`rotifer-mcp-server logout`.
|
|
363
401
|
- **Request bodies are capped at 256 KiB.** A larger request is answered `413`
|
|
364
402
|
with a JSON-RPC error before it is parsed. MCP messages are far smaller than
|
|
365
403
|
this; the cap exists because the SDK's default (4 MiB) is the bound
|
|
@@ -367,6 +405,55 @@ rotifer-mcp-server serve --host 0.0.0.0 # reachable from other machines
|
|
|
367
405
|
describes, where a single request can exhaust the server's memory.
|
|
368
406
|
- `GET /health` answers `{"status":"ok","transport":"streamable-http"}` for liveness checks.
|
|
369
407
|
|
|
408
|
+
To send the token from a client, each of these reads it at connection time, so it
|
|
409
|
+
stays out of the configuration file.
|
|
410
|
+
|
|
411
|
+
[Claude Code](https://code.claude.com/docs/en/mcp), in `.mcp.json`:
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"mcpServers": {
|
|
416
|
+
"rotifer": {
|
|
417
|
+
"type": "http",
|
|
418
|
+
"url": "https://mcp.example.com/mcp",
|
|
419
|
+
"headers": { "Authorization": "Bearer ${ROTIFER_MCP_HTTP_TOKEN}" }
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
[Cursor](https://cursor.com/docs/context/mcp), in `mcp.json`:
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"mcpServers": {
|
|
430
|
+
"rotifer": {
|
|
431
|
+
"url": "https://mcp.example.com/mcp",
|
|
432
|
+
"headers": { "Authorization": "Bearer ${env:ROTIFER_MCP_HTTP_TOKEN}" }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
[VS Code](https://code.visualstudio.com/docs/agents/reference/mcp-configuration), in `mcp.json`:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"inputs": [
|
|
443
|
+
{ "type": "promptString", "id": "rotifer-token", "description": "Rotifer MCP access token", "password": true }
|
|
444
|
+
],
|
|
445
|
+
"servers": {
|
|
446
|
+
"rotifer": {
|
|
447
|
+
"type": "http",
|
|
448
|
+
"url": "https://mcp.example.com/mcp",
|
|
449
|
+
"headers": { "Authorization": "Bearer ${input:rotifer-token}" }
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Claude Desktop uses stdio by default.
|
|
456
|
+
|
|
370
457
|
### Usage reporting
|
|
371
458
|
|
|
372
459
|
When you are **signed in**, each tool call reports a usage record to Rotifer
|
package/dist/auth.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { randomBytes, createHash } from "node:crypto";
|
|
5
|
+
import { renderCallbackPage } from "./callback-page.js";
|
|
5
6
|
const ROTIFER_HOME = join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".rotifer");
|
|
6
7
|
function ensureRotiferHome() {
|
|
7
8
|
if (!existsSync(ROTIFER_HOME)) {
|
|
@@ -81,45 +82,17 @@ export async function startOAuthCallbackServer(port = OAUTH_CALLBACK_PORT) {
|
|
|
81
82
|
const server = createServer((req, res) => {
|
|
82
83
|
const boundPort = server.address()?.port || 0;
|
|
83
84
|
const url = new URL(req.url || "/", `http://localhost:${boundPort}`);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (token) {
|
|
88
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
89
|
-
res.end("<html><body><h2>Login successful!</h2>" +
|
|
90
|
-
"<p>You can close this window and return to the terminal.</p>" +
|
|
91
|
-
"</body></html>");
|
|
92
|
-
server.close();
|
|
93
|
-
callbackResolve(`implicit:${token}:${refresh || ""}`);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
85
|
+
// Only a PKCE code ends the login: it is worthless without this process's
|
|
86
|
+
// code_verifier. A token handed to this port is never accepted, because any
|
|
87
|
+
// page open in the user's browser can make that request.
|
|
97
88
|
const code = url.searchParams.get("code");
|
|
89
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
90
|
+
res.end(renderCallbackPage(url.searchParams));
|
|
98
91
|
if (code) {
|
|
99
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
100
|
-
res.end("<html><body><h2>Login successful!</h2>" +
|
|
101
|
-
"<p>You can close this window and return to the terminal.</p>" +
|
|
102
|
-
"</body></html>");
|
|
103
92
|
server.close();
|
|
93
|
+
clearTimeout(timeLimit);
|
|
104
94
|
callbackResolve(code);
|
|
105
95
|
}
|
|
106
|
-
else {
|
|
107
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
108
|
-
res.end(`<html><body><script>
|
|
109
|
-
if (window.location.hash) {
|
|
110
|
-
var params = new URLSearchParams(window.location.hash.substring(1));
|
|
111
|
-
var token = params.get('access_token');
|
|
112
|
-
var refresh = params.get('refresh_token');
|
|
113
|
-
if (token) {
|
|
114
|
-
window.location.href = '/callback/token?access_token=' + encodeURIComponent(token) + '&refresh_token=' + encodeURIComponent(refresh || '');
|
|
115
|
-
} else {
|
|
116
|
-
document.body.innerHTML = '<h2>Login failed</h2><p>No token received.</p>';
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
document.body.innerHTML = '<h2>Login failed</h2><p>Missing authorization data.</p>';
|
|
120
|
-
}
|
|
121
|
-
</script><noscript>Enable JavaScript to complete login.</noscript></body></html>`);
|
|
122
|
-
}
|
|
123
96
|
});
|
|
124
97
|
server.listen(port, "127.0.0.1", () => {
|
|
125
98
|
const addr = server.address();
|
|
@@ -128,7 +101,7 @@ if (window.location.hash) {
|
|
|
128
101
|
server.on("error", (err) => {
|
|
129
102
|
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
130
103
|
});
|
|
131
|
-
setTimeout(() => {
|
|
104
|
+
const timeLimit = setTimeout(() => {
|
|
132
105
|
server.close();
|
|
133
106
|
callbackReject(new Error("Login timed out after 120 seconds"));
|
|
134
107
|
}, 120_000);
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderCallbackPage(params: URLSearchParams): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Colors, radii, type and logo follow rotifer.dev. Everything is inline because
|
|
2
|
+
// this page is served from the login callback port and must load nothing else.
|
|
3
|
+
const CSS = `:root { --bg: #09090b; --surface: #111113; --border: rgba(255, 255, 255, 0.08); --text: #fafafa; --text-1: #d4d4d8; --text-2: #a1a1aa; --overlay-6: rgba(255, 255, 255, 0.06); --color-success: #4ade80; --color-success-dim: rgba(74, 222, 128, 0.10); --color-danger: #f87171; --color-danger-dim: rgba(248, 113, 113, 0.10); --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08); --sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } @media (prefers-color-scheme: light) { :root { --bg: #ffffff; --surface: #f4f4f5; --border: rgba(0, 0, 0, 0.06); --text: #18181b; --text-1: #3f3f46; --text-2: #71717a; --overlay-6: rgba(0, 0, 0, 0.06); --color-success: #16a34a; --color-success-dim: rgba(22, 163, 74, 0.10); --color-danger: #dc2626; --color-danger-dim: rgba(220, 38, 38, 0.10); --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.04); } } * { margin: 0; padding: 0; box-sizing: border-box; } body { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; font-family: var(--sans); font-size: 1rem; line-height: 1.6; background: var(--bg); color: var(--text); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } header { height: 57px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 24px; border-bottom: 1px solid var(--border); font-size: 0.95rem; font-weight: 500; letter-spacing: -0.01em; } .logo { display: block; flex: none; } main { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px 16px 80px; } section { width: 100%; max-width: 440px; padding: 32px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-sm); } .status { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; border-radius: 8px; } .status.ok { color: var(--color-success); background: var(--color-success-dim); } .status.bad { color: var(--color-danger); background: var(--color-danger-dim); } .status.neutral { color: var(--text-2); background: var(--overlay-6); } h1 { font-size: 1.5rem; font-weight: 500; line-height: 1.2; letter-spacing: -0.02em; } p { margin-top: 8px; color: var(--text-1); }`;
|
|
4
|
+
const LOGO = `<svg viewBox="0 0 64 64" class="logo" width="22" height="22" aria-hidden="true"> <circle cx="32" cy="32" r="32" fill="#1a1a1a"/> <g transform="translate(0,64) scale(0.00625,-0.00625)" fill="white"> <path d="M4165 8874 c-107 -12 -433 -57 -535 -74 -145 -25 -211 -44 -277 -81 -28 -16 -107 -47 -175 -70 -85 -27 -143 -53 -189 -84 -36 -25 -100 -61 -143 -81 -43 -19 -97 -53 -120 -73 -22 -21 -72 -59 -111 -84 -149 -98 -450 -396 -546 -539 -23 -35 -61 -83 -84 -108 -23 -25 -54 -67 -69 -94 -16 -26 -50 -72 -77 -102 -27 -30 -62 -80 -78 -113 -17 -32 -53 -87 -81 -122 -29 -35 -69 -100 -89 -144 -21 -44 -56 -105 -79 -135 -30 -39 -51 -83 -72 -150 -17 -52 -48 -124 -70 -160 -22 -36 -52 -103 -66 -150 -49 -162 -111 -345 -137 -405 -45 -102 -30 -225 27 -225 28 0 46 19 81 90 15 30 53 82 85 115 31 33 75 92 98 130 74 128 195 260 514 560 44 42 88 94 108 130 18 33 54 85 80 115 26 30 61 80 79 112 18 31 49 76 70 100 21 23 60 77 87 118 26 41 69 98 94 125 26 28 62 70 81 95 45 61 221 228 284 270 28 18 74 55 103 82 30 26 91 66 140 89 47 22 100 52 116 65 44 38 111 68 211 97 50 14 132 44 182 66 152 66 360 95 703 95 332 0 533 -28 682 -95 48 -21 132 -52 187 -68 111 -33 168 -59 223 -103 21 -16 71 -43 112 -59 42 -18 97 -50 128 -76 29 -25 86 -68 126 -94 139 -92 408 -370 469 -485 18 -33 56 -93 86 -134 36 -48 64 -101 82 -154 16 -45 47 -120 71 -167 73 -147 103 -318 111 -639 9 -337 -30 -591 -112 -730 -25 -42 -54 -102 -64 -132 -26 -76 -35 -91 -103 -178 -32 -41 -75 -98 -96 -127 -58 -80 -253 -257 -335 -304 -39 -23 -90 -57 -112 -74 -52 -42 -81 -56 -185 -90 -47 -15 -107 -39 -135 -53 -27 -13 -74 -33 -103 -43 -187 -64 -719 -69 -903 -8 -31 10 -107 45 -168 78 -161 87 -206 96 -258 48 -44 -40 -83 -224 -83 -391 0 -81 10 -149 49 -344 48 -241 50 -245 99 -326 28 -45 59 -109 70 -142 26 -81 53 -132 97 -184 20 -24 52 -75 72 -113 19 -38 54 -90 77 -116 24 -25 58 -68 76 -96 105 -157 513 -577 681 -702 41 -30 94 -74 119 -98 25 -24 65 -51 89 -60 59 -21 179 -19 320 4 64 11 141 23 171 27 133 16 177 69 135 159 -26 53 -75 96 -150 130 -38 18 -92 52 -120 77 -27 25 -84 67 -125 95 -41 28 -88 65 -105 83 -16 17 -52 48 -80 67 -27 20 -79 66 -115 102 -36 36 -83 82 -105 102 -62 56 -184 194 -228 257 -21 31 -60 78 -86 104 -27 26 -64 77 -84 115 -19 37 -50 82 -67 98 -86 81 -76 175 18 175 37 0 50 -7 137 -69 54 -39 135 -72 255 -105 47 -13 117 -37 155 -54 39 -17 89 -35 111 -41 150 -40 545 -61 794 -42 213 16 320 36 420 78 47 19 123 47 170 60 138 40 200 64 253 101 28 19 94 54 148 77 58 25 119 61 151 87 29 25 77 58 107 73 30 15 77 48 105 74 28 26 69 60 91 76 69 50 259 232 331 317 38 45 92 107 120 137 28 31 68 83 89 116 21 33 55 80 76 105 21 25 50 74 65 110 15 36 49 97 75 135 25 39 62 102 82 140 55 110 89 129 130 73 32 -44 33 -151 3 -393 -15 -115 -40 -262 -56 -325 -17 -63 -45 -194 -64 -289 -30 -154 -41 -187 -84 -275 -28 -56 -64 -146 -81 -201 -22 -74 -43 -119 -79 -170 -27 -39 -64 -104 -81 -146 -18 -42 -48 -95 -69 -119 -20 -23 -62 -82 -92 -130 -85 -137 -280 -373 -390 -474 -24 -23 -72 -68 -105 -102 -33 -34 -82 -77 -110 -97 -27 -19 -63 -50 -80 -68 -16 -17 -68 -56 -115 -87 -47 -30 -104 -72 -127 -94 -24 -21 -76 -54 -116 -73 -40 -19 -91 -50 -113 -68 -45 -40 -96 -67 -189 -102 -37 -14 -94 -43 -126 -64 -65 -43 -100 -58 -236 -95 -50 -14 -131 -43 -180 -65 -102 -46 -165 -62 -358 -90 -533 -77 -691 -82 -1083 -31 -364 48 -459 68 -577 120 -60 26 -147 58 -193 69 -97 25 -178 59 -226 96 -18 14 -74 42 -122 61 -116 47 -132 56 -184 99 -24 20 -75 52 -114 70 -39 19 -93 54 -121 78 -27 24 -81 64 -120 89 -38 25 -86 62 -105 83 -19 20 -55 51 -80 68 -57 39 -369 354 -422 427 -23 30 -60 75 -83 100 -24 25 -68 86 -99 135 -31 50 -74 112 -96 139 -21 27 -48 76 -60 108 -11 32 -35 77 -53 100 -57 76 -66 94 -102 217 -19 67 -48 154 -65 195 -48 122 -61 197 -67 381 -7 209 11 346 67 520 21 66 50 168 64 228 26 110 48 161 97 233 15 22 44 81 64 130 21 55 53 111 80 144 25 30 61 83 80 117 20 34 57 86 83 115 27 29 68 78 91 108 54 68 239 244 306 290 28 18 78 58 113 87 35 29 97 68 138 88 41 19 89 46 106 60 44 37 111 69 198 95 41 12 118 41 171 65 164 75 385 105 764 105 217 0 397 -16 460 -40 44 -17 65 -40 65 -72 0 -36 -37 -60 -135 -89 -38 -11 -126 -42 -195 -69 -69 -26 -150 -56 -180 -65 -72 -23 -116 -46 -165 -85 -22 -18 -75 -50 -119 -72 -43 -22 -99 -58 -125 -82 -25 -23 -70 -59 -98 -80 -77 -55 -296 -288 -358 -381 -30 -44 -72 -102 -93 -130 -22 -27 -51 -81 -66 -120 -15 -38 -42 -95 -60 -125 -62 -101 -77 -142 -106 -269 -16 -69 -40 -163 -55 -209 -44 -139 -55 -218 -61 -422 -7 -233 7 -366 60 -551 19 -68 46 -178 60 -244 30 -146 43 -183 90 -260 20 -33 52 -108 71 -166 25 -73 49 -124 76 -160 23 -30 61 -96 85 -147 35 -76 60 -111 129 -185 104 -112 124 -126 228 -168 45 -17 108 -49 139 -71 35 -23 100 -52 170 -74 62 -20 159 -58 216 -85 112 -54 167 -62 195 -31 28 31 20 60 -31 115 -27 29 -63 73 -80 98 -17 25 -51 62 -76 83 -25 20 -67 65 -93 99 -25 34 -63 80 -83 102 -20 23 -51 69 -69 102 -18 34 -54 86 -79 115 -25 29 -62 90 -83 135 -21 45 -59 110 -84 144 -32 43 -54 89 -75 156 -16 52 -49 134 -74 181 -58 115 -70 158 -105 390 -51 336 -51 427 5 762 30 179 49 241 98 314 19 29 44 76 56 105 33 85 56 124 108 187 27 32 68 87 91 122 23 34 45 62 50 62 5 0 48 37 97 83 49 46 122 103 162 126 40 23 87 55 105 70 50 42 75 55 176 90 52 18 126 48 163 67 37 19 94 41 126 49 227 60 721 59 901 -2 25 -8 100 -45 166 -81 93 -50 132 -66 173 -70 90 -8 118 37 152 245 20 123 20 137 6 191 -25 100 -60 166 -106 203 -24 19 -73 62 -109 96 -36 33 -93 77 -126 96 -33 20 -85 57 -115 83 -30 26 -89 63 -130 81 -41 19 -99 53 -127 75 -37 28 -90 53 -179 84 -69 24 -145 55 -168 69 -73 43 -153 64 -391 105 -399 69 -427 73 -590 67 -117 -3 -209 -14 -420 -51 -323 -55 -365 -66 -475 -121 -47 -23 -129 -57 -183 -74 -53 -18 -119 -48 -145 -67 -26 -19 -92 -57 -147 -85 -55 -28 -118 -68 -140 -89 -23 -20 -70 -54 -105 -74 -36 -21 -96 -66 -134 -100 -127 -111 -388 -380 -429 -441 -23 -33 -62 -82 -88 -110 -25 -27 -59 -75 -74 -105 -15 -30 -46 -80 -70 -110 -49 -64 -65 -94 -99 -183 -13 -35 -45 -95 -70 -133 -37 -56 -52 -94 -81 -203 -19 -73 -50 -168 -69 -210 -44 -100 -62 -177 -80 -341 -24 -210 -30 -408 -17 -580 20 -265 43 -374 101 -490 15 -30 37 -89 49 -130 36 -126 52 -162 100 -235 26 -38 62 -105 80 -148 19 -42 56 -105 83 -138 27 -34 60 -84 74 -112 14 -27 45 -70 68 -96 23 -25 64 -77 90 -115 74 -108 348 -406 454 -496 31 -26 68 -60 82 -75 14 -15 68 -60 120 -100 52 -40 106 -85 120 -99 13 -15 49 -41 80 -58 30 -18 78 -52 105 -76 28 -25 82 -60 120 -77 39 -18 101 -55 138 -83 37 -28 89 -58 115 -68 73 -26 161 -69 222 -109 34 -21 96 -47 159 -65 56 -16 146 -49 199 -74 125 -57 133 -59 352 -101 102 -19 219 -44 260 -55 213 -58 419 -75 775 -67 287 7 341 14 560 68 88 21 223 50 300 64 165 30 214 45 315 95 41 21 118 50 170 65 103 30 147 49 224 101 28 18 89 47 135 64 50 18 112 51 150 79 36 27 100 65 142 85 42 20 98 55 125 79 27 25 72 56 100 70 28 14 75 48 105 75 30 27 83 70 119 96 97 70 400 365 507 493 51 61 98 115 104 118 6 4 27 33 46 64 20 31 58 80 85 110 27 30 63 81 80 114 16 33 51 88 76 121 26 34 58 86 71 116 53 120 64 142 87 169 35 42 70 117 93 203 11 42 43 119 70 170 43 80 55 116 85 253 19 88 50 205 70 260 19 55 40 125 46 155 40 198 62 758 41 1080 -18 282 -35 387 -85 542 -24 75 -54 185 -66 245 -29 147 -51 214 -104 313 -24 47 -54 123 -66 170 -29 116 -54 196 -96 308 -19 52 -44 131 -55 176 -27 111 -43 149 -93 220 -23 33 -53 86 -66 116 -28 67 -43 91 -96 155 -22 28 -58 77 -79 110 -65 104 -351 405 -440 465 -29 19 -80 56 -114 82 -38 28 -87 55 -128 68 -38 12 -117 46 -178 76 -110 54 -110 54 -378 99 -148 24 -321 47 -386 51 -171 10 -191 -7 -126 -104 44 -65 85 -92 200 -132 50 -17 117 -48 150 -69 33 -22 93 -57 134 -80 40 -23 94 -60 120 -82 25 -23 66 -53 91 -67 25 -14 72 -51 105 -82 33 -31 76 -68 96 -83 76 -55 347 -342 394 -416 15 -25 47 -65 70 -90 24 -26 59 -82 79 -125 20 -43 51 -100 70 -126 54 -75 60 -92 122 -325 59 -220 59 -220 59 -430 0 -138 -5 -224 -13 -251 -6 -23 -34 -148 -61 -278 -46 -221 -53 -243 -99 -329 -27 -51 -60 -123 -72 -162 -29 -90 -44 -118 -95 -185 -24 -30 -57 -83 -74 -117 -17 -34 -52 -88 -79 -120 -27 -32 -64 -81 -83 -109 -74 -111 -366 -390 -492 -471 -40 -25 -93 -63 -118 -85 -25 -21 -82 -53 -126 -72 -44 -18 -100 -47 -124 -63 -75 -52 -122 -71 -250 -103 -68 -17 -176 -47 -239 -66 -141 -43 -250 -53 -500 -49 -196 4 -239 11 -277 47 -33 30 -29 62 14 109 46 51 126 91 245 125 109 31 133 42 214 100 33 24 82 52 108 63 26 12 70 43 99 69 29 27 68 60 89 73 91 61 347 332 402 426 20 33 52 80 72 104 41 49 74 111 98 185 9 29 35 82 57 119 56 92 65 116 89 241 12 60 37 177 56 260 45 200 60 368 46 521 -13 140 -124 708 -148 755 -9 18 -31 58 -49 88 -18 30 -43 87 -55 125 -29 90 -44 118 -95 185 -24 30 -57 84 -75 119 -17 35 -51 84 -74 110 -23 25 -68 80 -98 121 -62 82 -284 308 -363 368 -27 20 -68 55 -90 76 -22 21 -62 49 -90 63 -27 14 -73 45 -102 70 -29 24 -91 62 -139 84 -47 22 -114 59 -149 83 -42 29 -97 54 -166 75 -57 18 -151 55 -209 81 -119 55 -190 70 -543 120 -206 29 -441 44 -532 34z"/> </g> </svg>`;
|
|
5
|
+
const ICONS = {
|
|
6
|
+
check: '<path d="M5 12.5 10 17.5 19 7.5"/>',
|
|
7
|
+
minus: '<path d="M6 12h12"/>',
|
|
8
|
+
cross: '<path d="M7 7l10 10M17 7 7 17"/>',
|
|
9
|
+
};
|
|
10
|
+
const TRY_AGAIN = "To try again, start login from your terminal.";
|
|
11
|
+
/**
|
|
12
|
+
* Every page is fixed text. Nothing from the callback URL may be written into
|
|
13
|
+
* one: any web page can open this port while a login is waiting, with whatever
|
|
14
|
+
* parameters it likes.
|
|
15
|
+
*/
|
|
16
|
+
const PAGES = {
|
|
17
|
+
success: { icon: ICONS.check, tone: "ok", title: "Authorization complete", text: "Return to your terminal. You can close this tab." },
|
|
18
|
+
canceled: { icon: ICONS.minus, tone: "neutral", title: "Login canceled", text: `Nothing was changed. ${TRY_AGAIN}` },
|
|
19
|
+
failed: { icon: ICONS.cross, tone: "bad", title: "Login failed", text: TRY_AGAIN },
|
|
20
|
+
incomplete: { icon: ICONS.cross, tone: "bad", title: "Login didn't finish", text: `This page opened before login was complete. ${TRY_AGAIN}` },
|
|
21
|
+
};
|
|
22
|
+
/** Picks the page for a callback request. Only decides what to show, not how the login ends. */
|
|
23
|
+
function pageFor(params) {
|
|
24
|
+
if (params.get("code"))
|
|
25
|
+
return "success";
|
|
26
|
+
const error = params.get("error");
|
|
27
|
+
if (error === "access_denied")
|
|
28
|
+
return "canceled";
|
|
29
|
+
return error ? "failed" : "incomplete";
|
|
30
|
+
}
|
|
31
|
+
export function renderCallbackPage(params) {
|
|
32
|
+
const { icon, tone, title, text } = PAGES[pageFor(params)];
|
|
33
|
+
return (`<!doctype html><html lang="en"><head><meta charset="utf-8">` +
|
|
34
|
+
`<meta name="viewport" content="width=device-width, initial-scale=1">` +
|
|
35
|
+
`<meta name="color-scheme" content="dark light">` +
|
|
36
|
+
`<title>${title} - Rotifer Protocol</title><link rel="icon" href="data:,"><style>${CSS}</style></head>` +
|
|
37
|
+
`<body><header>${LOGO}<span>Rotifer Protocol</span></header><main><section>` +
|
|
38
|
+
`<div class="status ${tone}"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" ` +
|
|
39
|
+
`stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icon}</svg></div>` +
|
|
40
|
+
`<h1>${title}</h1><p>${text}</p></section></main></body></html>`);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=callback-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-page.js","sourceRoot":"","sources":["../src/callback-page.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,+EAA+E;AAC/E,MAAM,GAAG,GAAG,qtEAAqtE,CAAC;AAEluE,MAAM,IAAI,GAAG,glRAAglR,CAAC;AAE9lR,MAAM,KAAK,GAAG;IACZ,KAAK,EAAE,oCAAoC;IAC3C,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,kCAAkC;CAC1C,CAAC;AAEF,MAAM,SAAS,GAAG,+CAA+C,CAAC;AAElE;;;;GAIG;AACH,MAAM,KAAK,GAAG;IACZ,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,wBAAwB,EAAE,IAAI,EAAE,kDAAkD,EAAE;IACrI,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,wBAAwB,SAAS,EAAE,EAAE;IACpH,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE;IAClF,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,+CAA+C,SAAS,EAAE,EAAE;CACtI,CAAC;AAEX,gGAAgG;AAChG,SAAS,OAAO,CAAC,MAAuB;IACtC,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,eAAe;QAAE,OAAO,UAAU,CAAC;IACjD,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,OAAO,CACL,6DAA6D;QAC7D,sEAAsE;QACtE,iDAAiD;QACjD,UAAU,KAAK,oEAAoE,GAAG,iBAAiB;QACvG,iBAAiB,IAAI,uDAAuD;QAC5E,sBAAsB,IAAI,sFAAsF;QAChH,sFAAsF,IAAI,cAAc;QACxG,OAAO,KAAK,WAAW,IAAI,qCAAqC,CACjE,CAAC;AACJ,CAAC"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
/**
|
|
3
|
-
* Loopback unless the operator asks otherwise (`serve --host`).
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Loopback unless the operator asks otherwise (`serve --host`). Its tools act
|
|
4
|
+
* with the signed-in user's Rotifer account, so it should not be reachable from
|
|
5
|
+
* other machines by default, and `serve` refuses to be without an access token.
|
|
6
|
+
* Before GHSA-46jr was fixed here it listened on every interface.
|
|
7
7
|
*/
|
|
8
8
|
export declare const DEFAULT_HOST = "127.0.0.1";
|
|
9
9
|
/**
|
|
@@ -14,7 +14,31 @@ export declare const DEFAULT_HOST = "127.0.0.1";
|
|
|
14
14
|
* nothing. A larger body is answered 413 before it is parsed.
|
|
15
15
|
*/
|
|
16
16
|
export declare const MAX_REQUEST_BODY_BYTES: number;
|
|
17
|
+
/** Read once at launch and then removed, so spawned CLIs and genes never inherit it. */
|
|
18
|
+
export declare const ACCESS_TOKEN_ENV = "ROTIFER_MCP_HTTP_TOKEN";
|
|
19
|
+
/**
|
|
20
|
+
* The names `serve` accepts besides loopback. Listening beyond this machine
|
|
21
|
+
* without naming them is refused, because the server would otherwise answer
|
|
22
|
+
* whatever name a client used to reach it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveAllowedHosts(host: string | undefined, allowedHosts: string | undefined): string[];
|
|
25
|
+
/**
|
|
26
|
+
* The token HTTP clients must present, or undefined for none. Required when
|
|
27
|
+
* other machines can reach the server. Never taken from the command line,
|
|
28
|
+
* because other programs on the machine can read that.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveAccessToken(host: string | undefined, tokenFile: string | undefined, envToken: string | undefined): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Why an MCP request must be refused, or null to serve it. A web page can make
|
|
33
|
+
* a browser reach this server under the page's own name by re-pointing that
|
|
34
|
+
* name at 127.0.0.1 (DNS rebinding), so the listening address alone protects
|
|
35
|
+
* nothing: the name the client used has to be one this server was told about.
|
|
36
|
+
* Clients that are not browsers send no Origin, so only Host applies to them.
|
|
37
|
+
*/
|
|
38
|
+
export declare function refuseRequest(headers: http.IncomingHttpHeaders, allowed: ReadonlySet<string>): string | null;
|
|
17
39
|
export declare function startHttpServer(opts?: {
|
|
18
40
|
port?: number;
|
|
19
41
|
host?: string;
|
|
42
|
+
allowedHosts?: string[];
|
|
43
|
+
accessToken?: string;
|
|
20
44
|
}): Promise<http.Server>;
|
package/dist/http.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
1
3
|
import http from "node:http";
|
|
4
|
+
import { isIPv6 } from "node:net";
|
|
2
5
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
6
|
import { createServer } from "./server.js";
|
|
4
7
|
const DEFAULT_PORT = 3000;
|
|
5
8
|
/**
|
|
6
|
-
* Loopback unless the operator asks otherwise (`serve --host`).
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Loopback unless the operator asks otherwise (`serve --host`). Its tools act
|
|
10
|
+
* with the signed-in user's Rotifer account, so it should not be reachable from
|
|
11
|
+
* other machines by default, and `serve` refuses to be without an access token.
|
|
12
|
+
* Before GHSA-46jr was fixed here it listened on every interface.
|
|
10
13
|
*/
|
|
11
14
|
export const DEFAULT_HOST = "127.0.0.1";
|
|
12
15
|
/**
|
|
@@ -17,18 +20,130 @@ export const DEFAULT_HOST = "127.0.0.1";
|
|
|
17
20
|
* nothing. A larger body is answered 413 before it is parsed.
|
|
18
21
|
*/
|
|
19
22
|
export const MAX_REQUEST_BODY_BYTES = 256 * 1024;
|
|
23
|
+
/** Read once at launch and then removed, so spawned CLIs and genes never inherit it. */
|
|
24
|
+
export const ACCESS_TOKEN_ENV = "ROTIFER_MCP_HTTP_TOKEN";
|
|
20
25
|
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "localhost"]);
|
|
26
|
+
/** Loopback as it appears in a Host or Origin header. Always accepted. */
|
|
27
|
+
const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "[::1]"];
|
|
28
|
+
const HOST_HEADER = /^(\[[0-9a-f:.]+\]|[a-z0-9.-]+)(?::\d{1,5})?$/;
|
|
29
|
+
const ALLOWED_HOSTS_USAGE = "--allowed-hosts takes host names or IP addresses separated by commas, with no scheme, port, path or wildcard, " +
|
|
30
|
+
"for example --allowed-hosts mcp.example.com,192.168.1.20";
|
|
31
|
+
function normalizeAllowedHost(entry) {
|
|
32
|
+
const name = entry.trim().toLowerCase();
|
|
33
|
+
if (isIPv6(name))
|
|
34
|
+
return `[${name}]`;
|
|
35
|
+
if (name.startsWith("[") && name.endsWith("]") && isIPv6(name.slice(1, -1)))
|
|
36
|
+
return name;
|
|
37
|
+
if (/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/.test(name))
|
|
38
|
+
return name;
|
|
39
|
+
throw new Error(ALLOWED_HOSTS_USAGE);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The names `serve` accepts besides loopback. Listening beyond this machine
|
|
43
|
+
* without naming them is refused, because the server would otherwise answer
|
|
44
|
+
* whatever name a client used to reach it.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveAllowedHosts(host, allowedHosts) {
|
|
47
|
+
if (allowedHosts === undefined) {
|
|
48
|
+
if (host !== undefined && !LOOPBACK_HOSTS.has(host)) {
|
|
49
|
+
throw new Error(`--host ${host} also needs --allowed-hosts, naming the addresses clients will use to reach this server, ` +
|
|
50
|
+
"for example --allowed-hosts mcp.example.com,192.168.1.20");
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return allowedHosts.split(",").map(normalizeAllowedHost);
|
|
55
|
+
}
|
|
56
|
+
function readTokenFile(path) {
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = readFileSync(path, "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new Error(`--token-file ${path} cannot be read (${err.code}). Check the path and its permissions.`);
|
|
63
|
+
}
|
|
64
|
+
// `echo x > file` ends the file with a newline that is not part of the token.
|
|
65
|
+
const token = text.replace(/[\r\n]+$/, "");
|
|
66
|
+
if (!token) {
|
|
67
|
+
throw new Error(`--token-file ${path} is empty. Put a token in it, for example: openssl rand -hex 32 > ${path}`);
|
|
68
|
+
}
|
|
69
|
+
return token;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The token HTTP clients must present, or undefined for none. Required when
|
|
73
|
+
* other machines can reach the server. Never taken from the command line,
|
|
74
|
+
* because other programs on the machine can read that.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveAccessToken(host, tokenFile, envToken) {
|
|
77
|
+
if (tokenFile !== undefined && envToken !== undefined) {
|
|
78
|
+
throw new Error(`Both --token-file and ${ACCESS_TOKEN_ENV} are set. Use only one of them.`);
|
|
79
|
+
}
|
|
80
|
+
if (tokenFile !== undefined)
|
|
81
|
+
return readTokenFile(tokenFile);
|
|
82
|
+
if (envToken === "") {
|
|
83
|
+
throw new Error(`${ACCESS_TOKEN_ENV} is set but empty. Set it to a token, for example the output of: openssl rand -hex 32`);
|
|
84
|
+
}
|
|
85
|
+
if (envToken === undefined && host !== undefined && !LOOPBACK_HOSTS.has(host)) {
|
|
86
|
+
throw new Error(`--host ${host} also needs an access token, because other machines can reach it. ` +
|
|
87
|
+
`Create one with: openssl rand -hex 32 > mcp-token, then add --token-file mcp-token or set ${ACCESS_TOKEN_ENV}`);
|
|
88
|
+
}
|
|
89
|
+
return envToken;
|
|
90
|
+
}
|
|
91
|
+
const sha256 = (value) => createHash("sha256").update(value).digest();
|
|
92
|
+
/** Compares fixed-length digests, so timing reveals neither the token nor its length. */
|
|
93
|
+
function tokenAccepted(authorization, token) {
|
|
94
|
+
const presented = /^bearer +(.+)$/i.exec(authorization ?? "")?.[1];
|
|
95
|
+
return presented !== undefined && timingSafeEqual(sha256(presented), sha256(token));
|
|
96
|
+
}
|
|
97
|
+
function originAllowed(origin, allowed) {
|
|
98
|
+
try {
|
|
99
|
+
const url = new URL(origin);
|
|
100
|
+
return (url.protocol === "http:" || url.protocol === "https:") && allowed.has(url.hostname.toLowerCase());
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Why an MCP request must be refused, or null to serve it. A web page can make
|
|
108
|
+
* a browser reach this server under the page's own name by re-pointing that
|
|
109
|
+
* name at 127.0.0.1 (DNS rebinding), so the listening address alone protects
|
|
110
|
+
* nothing: the name the client used has to be one this server was told about.
|
|
111
|
+
* Clients that are not browsers send no Origin, so only Host applies to them.
|
|
112
|
+
*/
|
|
113
|
+
export function refuseRequest(headers, allowed) {
|
|
114
|
+
const hostname = HOST_HEADER.exec((headers.host ?? "").toLowerCase())?.[1];
|
|
115
|
+
if (!hostname || !allowed.has(hostname)) {
|
|
116
|
+
return "This server does not accept requests addressed to that Host. To reach it under another name, restart it with: rotifer-mcp-server serve --allowed-hosts <name>";
|
|
117
|
+
}
|
|
118
|
+
if (headers.origin !== undefined && !originAllowed(headers.origin, allowed)) {
|
|
119
|
+
return "This server does not accept requests from that Origin. To allow a site, restart the server with: rotifer-mcp-server serve --allowed-hosts <name>";
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
21
123
|
export async function startHttpServer(opts) {
|
|
22
124
|
const port = opts?.port ?? parseInt(process.env.PORT || String(DEFAULT_PORT), 10);
|
|
23
125
|
const host = opts?.host ?? DEFAULT_HOST;
|
|
126
|
+
const allowed = new Set([...LOOPBACK_HOSTNAMES, ...(opts?.allowedHosts ?? [])]);
|
|
127
|
+
const accessToken = opts?.accessToken;
|
|
128
|
+
const reject = (res, status, message, headers = {}) => {
|
|
129
|
+
res.writeHead(status, { "Content-Type": "application/json", ...headers });
|
|
130
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message }, id: null }));
|
|
131
|
+
};
|
|
24
132
|
const httpServer = http.createServer(async (req, res) => {
|
|
25
133
|
const origin = req.headers.origin || "";
|
|
26
|
-
const allowedOrigin =
|
|
134
|
+
const allowedOrigin = originAllowed(origin, allowed) ? origin : "";
|
|
27
135
|
res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
|
|
28
136
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
29
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, mcp-session-id");
|
|
137
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, mcp-session-id");
|
|
30
138
|
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
139
|
+
// A browser's preflight cannot carry Authorization, so it is exempt from the
|
|
140
|
+
// token. It answers with CORS headers only and never reaches the MCP handler.
|
|
31
141
|
if (req.method === "OPTIONS") {
|
|
142
|
+
const refusal = refuseRequest(req.headers, allowed);
|
|
143
|
+
if (refusal) {
|
|
144
|
+
reject(res, 403, refusal);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
32
147
|
res.writeHead(204);
|
|
33
148
|
res.end();
|
|
34
149
|
return;
|
|
@@ -39,13 +154,24 @@ export async function startHttpServer(opts) {
|
|
|
39
154
|
res.end(JSON.stringify({ status: "ok", transport: "streamable-http" }));
|
|
40
155
|
return;
|
|
41
156
|
}
|
|
157
|
+
const refusal = refuseRequest(req.headers, allowed);
|
|
158
|
+
if (refusal) {
|
|
159
|
+
reject(res, 403, refusal);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (accessToken !== undefined && !tokenAccepted(req.headers.authorization, accessToken)) {
|
|
163
|
+
reject(res, 401, "This server requires its access token. Send it in the header: Authorization: Bearer <token>", {
|
|
164
|
+
"WWW-Authenticate": "Bearer",
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
42
168
|
if (url === "/mcp" || url === "/") {
|
|
43
169
|
try {
|
|
44
170
|
const transport = new StreamableHTTPServerTransport({
|
|
45
171
|
sessionIdGenerator: undefined,
|
|
46
172
|
maxRequestBodySize: MAX_REQUEST_BODY_BYTES,
|
|
47
173
|
});
|
|
48
|
-
const server = createServer();
|
|
174
|
+
const server = createServer({ transport: "http" });
|
|
49
175
|
res.on("close", () => {
|
|
50
176
|
transport.close();
|
|
51
177
|
server.close();
|
|
@@ -71,7 +197,8 @@ export async function startHttpServer(opts) {
|
|
|
71
197
|
console.error(`Rotifer MCP Server (Streamable HTTP) listening on http://${shown}:${port}/mcp`);
|
|
72
198
|
if (!LOOPBACK_HOSTS.has(host)) {
|
|
73
199
|
console.error(`Warning: listening on ${host}, so other machines can reach this server. ` +
|
|
74
|
-
"
|
|
200
|
+
"Anyone with the access token acts as your signed-in Rotifer account, and this server does not encrypt traffic: " +
|
|
201
|
+
"put it behind an HTTPS reverse proxy or tunnel.");
|
|
75
202
|
}
|
|
76
203
|
});
|
|
77
204
|
const shutdown = () => {
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,GAAG,IAAI,CAAC;AAEjD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;AAElE,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ async function main() {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
if (subcommand === "serve") {
|
|
42
|
-
const { startHttpServer } = await import("./http.js");
|
|
42
|
+
const { startHttpServer, resolveAllowedHosts, resolveAccessToken, ACCESS_TOKEN_ENV } = await import("./http.js");
|
|
43
43
|
const portIdx = process.argv.indexOf("--port");
|
|
44
44
|
const port = portIdx !== -1 ? parseInt(process.argv[portIdx + 1], 10) : undefined;
|
|
45
45
|
// Loopback by default; `--host 0.0.0.0` (or an interface address) to
|
|
@@ -50,11 +50,41 @@ async function main() {
|
|
|
50
50
|
console.error("serve: --host needs an address, e.g. --host 0.0.0.0");
|
|
51
51
|
process.exit(2);
|
|
52
52
|
}
|
|
53
|
+
const allowedIdx = process.argv.indexOf("--allowed-hosts");
|
|
54
|
+
const allowedValue = allowedIdx !== -1 ? process.argv[allowedIdx + 1] : undefined;
|
|
55
|
+
if (allowedIdx !== -1 && (!allowedValue || allowedValue.startsWith("--"))) {
|
|
56
|
+
console.error("serve: --allowed-hosts needs a value, e.g. --allowed-hosts mcp.example.com");
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
// Refused rather than ignored, so a token given this way cannot leave the
|
|
60
|
+
// server running without one. `--token-file=path` is caught for the same reason.
|
|
61
|
+
if (process.argv.some((arg) => arg === "--token" || /^--token(-file)?=/.test(arg))) {
|
|
62
|
+
console.error(`serve: give the access token with --token-file <path> or ${ACCESS_TOKEN_ENV}. ` +
|
|
63
|
+
"It is not accepted on the command line, which other programs on this machine can read.");
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
const tokenFileIdx = process.argv.indexOf("--token-file");
|
|
67
|
+
const tokenFile = tokenFileIdx !== -1 ? process.argv[tokenFileIdx + 1] : undefined;
|
|
68
|
+
if (tokenFileIdx !== -1 && (!tokenFile || tokenFile.startsWith("--"))) {
|
|
69
|
+
console.error("serve: --token-file needs a path, e.g. --token-file ./mcp-token");
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
let allowedHosts = [];
|
|
73
|
+
let accessToken;
|
|
74
|
+
try {
|
|
75
|
+
allowedHosts = resolveAllowedHosts(host, allowedValue);
|
|
76
|
+
accessToken = resolveAccessToken(host, tokenFile, process.env[ACCESS_TOKEN_ENV]);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error(`serve: ${err.message}`);
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
delete process.env[ACCESS_TOKEN_ENV];
|
|
53
83
|
notifyVersionOnStderr();
|
|
54
84
|
// Sessions build their server per request; build one now so an unusable
|
|
55
85
|
// --tools stops `serve` at launch, not on the first request.
|
|
56
86
|
createServer();
|
|
57
|
-
await startHttpServer({ port, host });
|
|
87
|
+
await startHttpServer({ port, host, allowedHosts, accessToken });
|
|
58
88
|
return;
|
|
59
89
|
}
|
|
60
90
|
if (subcommand === "--version" || subcommand === "-v") {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,UAAU,qBAAqB;IAClC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB;QAAE,OAAO;IACpG,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,cAAc,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0CAA0C,IAAI,CAAC,OAAO,MAAM,IAAI,CAAC,MAAM,IAAI;gBAC3E,4CAA4C,CAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,QAAQ,CAAC;YACb,QAAQ,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACxE,QAAQ,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SACzE,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACjD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,IAAI,UAAU,KAAK,aAAa,EAAE,CAAC;QACjC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC3D,MAAM,aAAa,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IAED,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|