@interstellar-tools/mcp 0.2.0 → 0.2.1
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 +6 -2
- package/dist/bin.js +13 -6
- package/dist/bin.js.map +1 -1
- package/dist/http.d.ts +12 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +37 -1
- package/dist/http.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,8 @@ claude mcp add interstellar-tools -- npx -y @interstellar-tools/mcp
|
|
|
44
44
|
|
|
45
45
|
Web assistants such as claude.ai, ChatGPT and Mistral Le Chat connect to remote
|
|
46
46
|
servers only. Serve Streamable HTTP and put it behind HTTPS (a tunnel such as
|
|
47
|
-
`cloudflared tunnel --url http://localhost:3000
|
|
47
|
+
`cloudflared tunnel --url http://localhost:3000 --http-host-header localhost:3000`
|
|
48
|
+
is enough for testing):
|
|
48
49
|
|
|
49
50
|
```shell
|
|
50
51
|
npx -y @interstellar-tools/mcp --http --port 3000
|
|
@@ -52,7 +53,10 @@ npx -y @interstellar-tools/mcp --http --port 3000
|
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
The server is stateless and binds to `127.0.0.1` unless you pass
|
|
55
|
-
`--host 0.0.0.0`.
|
|
56
|
+
`--host 0.0.0.0`. On loopback it only answers requests addressed to `localhost`,
|
|
57
|
+
`127.0.0.1` or `[::1]`, which protects it from DNS rebinding; pass
|
|
58
|
+
`--allowed-hosts` with your public host name when you serve it elsewhere.
|
|
59
|
+
Step-by-step setup for each assistant is in the
|
|
56
60
|
[MCP guide](https://phun-ky.net/interstellar-tools/guide/mcp/).
|
|
57
61
|
|
|
58
62
|
## Units
|
package/dist/bin.js
CHANGED
|
@@ -9,23 +9,30 @@ const { values } = parseArgs({
|
|
|
9
9
|
http: { type: 'boolean', default: false },
|
|
10
10
|
port: { type: 'string', default: process.env.PORT ?? '3000' },
|
|
11
11
|
host: { type: 'string', default: '127.0.0.1' },
|
|
12
|
+
'allowed-hosts': { type: 'string' },
|
|
12
13
|
help: { type: 'boolean', short: 'h', default: false }
|
|
13
14
|
}
|
|
14
15
|
});
|
|
15
16
|
if (values.help) {
|
|
16
17
|
process.stdout
|
|
17
|
-
.write(`Usage: interstellar-tools-mcp [--http] [--port <n>] [--host <addr>]
|
|
18
|
+
.write(`Usage: interstellar-tools-mcp [--http] [--port <n>] [--host <addr>] [--allowed-hosts <names>]
|
|
18
19
|
|
|
19
|
-
(default)
|
|
20
|
-
--http
|
|
21
|
-
--port <n>
|
|
22
|
-
--host <addr>
|
|
20
|
+
(default) Serve over stdio, for clients that launch the server themselves
|
|
21
|
+
--http Serve Streamable HTTP at http://<host>:<port>/mcp
|
|
22
|
+
--port <n> HTTP port (default: $PORT or 3000)
|
|
23
|
+
--host <addr> HTTP bind address (default: 127.0.0.1)
|
|
24
|
+
--allowed-hosts <names> Comma-separated host names requests may use
|
|
25
|
+
(default: localhost, 127.0.0.1 and [::1] on loopback)
|
|
23
26
|
`);
|
|
24
27
|
}
|
|
25
28
|
else if (values.http) {
|
|
26
29
|
const http = await startHttpServer({
|
|
27
30
|
port: Number(values.port),
|
|
28
|
-
host: values.host
|
|
31
|
+
host: values.host,
|
|
32
|
+
allowedHosts: values['allowed-hosts']
|
|
33
|
+
?.split(',')
|
|
34
|
+
.map((name) => name.trim())
|
|
35
|
+
.filter(Boolean)
|
|
29
36
|
});
|
|
30
37
|
const { address, port } = http.address();
|
|
31
38
|
// stdout stays clean for stdio clients; logs go to stderr
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE;QAC7D,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE;QAC9C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;KACtD;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,MAAM;SACX,KAAK,CAAC
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE;QAC7D,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE;QAC9C,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;KACtD;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,MAAM;SACX,KAAK,CAAC;;;;;;;;CAQV,CAAC,CAAC;AACH,CAAC;KAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC;QACjC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,YAAY,EAAE,MAAM,CAAC,eAAe,CAAC;YACnC,EAAE,KAAK,CAAC,GAAG,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,OAAO,CAAC;KACnB,CAAC,CAAC;IACH,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,EAAiB,CAAC;IAExD,0DAA0D;IAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qDAAqD,OAAO,IAAI,IAAI,QAAQ,CAC7E,CAAC;AACJ,CAAC;KAAM,CAAC;IACN,MAAM,YAAY,EAAE,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/http.d.ts
CHANGED
|
@@ -6,12 +6,23 @@ export interface HttpServerOptionsInterface {
|
|
|
6
6
|
host?: string;
|
|
7
7
|
/** Path that serves MCP requests. */
|
|
8
8
|
path?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Host names a request's `Host` header may name. Defaults to the loopback
|
|
11
|
+
* names when bound to loopback, which blocks DNS rebinding: a web page that
|
|
12
|
+
* points its own domain at 127.0.0.1 still sends that domain as `Host`.
|
|
13
|
+
* Required to restrict hosts when binding to another interface.
|
|
14
|
+
*/
|
|
15
|
+
allowedHosts?: string[];
|
|
9
16
|
}
|
|
10
17
|
/**
|
|
11
18
|
* Serve the MCP server over Streamable HTTP, for clients that only accept
|
|
12
19
|
* remote servers. Runs stateless: every request gets a fresh server, which is
|
|
13
20
|
* cheap because every tool is a pure calculation.
|
|
14
21
|
*
|
|
22
|
+
* Requests whose `Host` header isn't allowed are rejected with 403 before they
|
|
23
|
+
* reach the server. Bound to loopback (the default), only `localhost`,
|
|
24
|
+
* `127.0.0.1` and `[::1]` are allowed, which protects against DNS rebinding.
|
|
25
|
+
*
|
|
15
26
|
* @example
|
|
16
27
|
* ```ts
|
|
17
28
|
* import { startHttpServer } from '@interstellar-tools/mcp';
|
|
@@ -19,5 +30,5 @@ export interface HttpServerOptionsInterface {
|
|
|
19
30
|
* await startHttpServer({ port: 3000 }); // http://127.0.0.1:3000/mcp
|
|
20
31
|
* ```
|
|
21
32
|
*/
|
|
22
|
-
export declare const startHttpServer: ({ port, host, path }: HttpServerOptionsInterface) => Promise<Server>;
|
|
33
|
+
export declare const startHttpServer: ({ port, host, path, allowedHosts }: HttpServerOptionsInterface) => Promise<Server>;
|
|
23
34
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAC;AAInB,MAAM,WAAW,0BAA0B;IACzC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAkCD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,GAAI,oCAK7B,0BAA0B,KAAG,OAAO,CAAC,MAAM,CAmD7C,CAAC"}
|
package/dist/http.js
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
2
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
3
|
import { createServer } from './server.js';
|
|
4
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
5
|
+
const LOOPBACK_HOSTS = ['127.0.0.1', 'localhost', '[::1]'];
|
|
6
|
+
/** Answer a rejected request with a JSON-RPC error, without reading its body. */
|
|
7
|
+
const reject = (res, message) => {
|
|
8
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(JSON.stringify({
|
|
9
|
+
jsonrpc: '2.0',
|
|
10
|
+
error: { code: -32000, message },
|
|
11
|
+
id: null
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
14
|
+
/** The request's `Host` name if allowed, or the reason it isn't. */
|
|
15
|
+
const checkHost = (req, allowed) => {
|
|
16
|
+
const header = req.headers.host;
|
|
17
|
+
if (!header)
|
|
18
|
+
return { ok: false, reason: 'Missing Host header' };
|
|
19
|
+
try {
|
|
20
|
+
const { hostname } = new URL(`http://${header}`);
|
|
21
|
+
return allowed.includes(hostname)
|
|
22
|
+
? { ok: true }
|
|
23
|
+
: { ok: false, reason: `Invalid Host: ${hostname}` };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { ok: false, reason: `Invalid Host header: ${header}` };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
4
29
|
/**
|
|
5
30
|
* Serve the MCP server over Streamable HTTP, for clients that only accept
|
|
6
31
|
* remote servers. Runs stateless: every request gets a fresh server, which is
|
|
7
32
|
* cheap because every tool is a pure calculation.
|
|
8
33
|
*
|
|
34
|
+
* Requests whose `Host` header isn't allowed are rejected with 403 before they
|
|
35
|
+
* reach the server. Bound to loopback (the default), only `localhost`,
|
|
36
|
+
* `127.0.0.1` and `[::1]` are allowed, which protects against DNS rebinding.
|
|
37
|
+
*
|
|
9
38
|
* @example
|
|
10
39
|
* ```ts
|
|
11
40
|
* import { startHttpServer } from '@interstellar-tools/mcp';
|
|
@@ -13,8 +42,15 @@ import { createServer } from './server.js';
|
|
|
13
42
|
* await startHttpServer({ port: 3000 }); // http://127.0.0.1:3000/mcp
|
|
14
43
|
* ```
|
|
15
44
|
*/
|
|
16
|
-
export const startHttpServer = ({ port, host = '127.0.0.1', path = '/mcp' }) => {
|
|
45
|
+
export const startHttpServer = ({ port, host = '127.0.0.1', path = '/mcp', allowedHosts = LOOPBACK.includes(host) ? LOOPBACK_HOSTS : undefined }) => {
|
|
17
46
|
const http = createHttpServer(async (req, res) => {
|
|
47
|
+
if (allowedHosts) {
|
|
48
|
+
const check = checkHost(req, allowedHosts);
|
|
49
|
+
if (!check.ok) {
|
|
50
|
+
reject(res, check.reason);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
18
54
|
const { pathname } = new URL(req.url ?? '/', 'http://localhost');
|
|
19
55
|
if (pathname !== path) {
|
|
20
56
|
res.writeHead(404, { 'content-type': 'text/plain' }).end('Not found');
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,OAAO,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED