@everfur/cli 0.1.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.
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ import { buildServer } from './chunk-XMIERMOW.js';
3
+ import { createServer } from 'http';
4
+ import { timingSafeEqual } from 'crypto';
5
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
6
+
7
+ var DEFAULT_HOST = "127.0.0.1";
8
+ var DEFAULT_PORT = 3100;
9
+ var DEFAULT_PATH = "/mcp";
10
+ var MIN_ACCESS_TOKEN_LENGTH = 32;
11
+ function requireValue(env, name) {
12
+ const value = env[name]?.trim();
13
+ if (!value) throw new Error(`${name} is required to run the HTTP MCP server.`);
14
+ return value;
15
+ }
16
+ function parsePort(raw) {
17
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_PORT;
18
+ if (!/^\d+$/.test(raw)) throw new Error("EVERFUR_MCP_HTTP_PORT must be an integer between 0 and 65535.");
19
+ const value = Number(raw);
20
+ if (!Number.isSafeInteger(value) || value < 0 || value > 65535) {
21
+ throw new Error("EVERFUR_MCP_HTTP_PORT must be an integer between 0 and 65535.");
22
+ }
23
+ return value;
24
+ }
25
+ function parsePath(raw) {
26
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_PATH;
27
+ const path = raw.trim();
28
+ if (!path.startsWith("/") || path.includes("?") || path.includes("#")) {
29
+ throw new Error("EVERFUR_MCP_HTTP_PATH must be an absolute path with no query or fragment.");
30
+ }
31
+ return path;
32
+ }
33
+ function isLoopbackHost(host) {
34
+ const normalized = host.toLowerCase();
35
+ return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1" || normalized === "[::1]";
36
+ }
37
+ function parseCsv(raw, variable) {
38
+ if (raw === void 0 || raw.trim() === "") return /* @__PURE__ */ new Set();
39
+ const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
40
+ if (values.length === 0) throw new Error(`${variable} must contain at least one value when set.`);
41
+ return new Set(values);
42
+ }
43
+ function hostnameOf(header) {
44
+ if (!header) return null;
45
+ try {
46
+ return new URL(`http://${header}`).hostname.toLowerCase();
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function safeEquals(presented, expected) {
52
+ const actual = Buffer.from(presented);
53
+ const required = Buffer.from(expected);
54
+ return actual.length === required.length && timingSafeEqual(actual, required);
55
+ }
56
+ function writeJson(res, status, body, headers = {}) {
57
+ if (res.headersSent) return;
58
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", ...headers });
59
+ res.end(JSON.stringify(body));
60
+ }
61
+ async function serveStatelessMcp(req, res, mcp, parsedBody) {
62
+ const transport = new StreamableHTTPServerTransport({
63
+ enableJsonResponse: true
64
+ });
65
+ try {
66
+ await mcp.connect(transport);
67
+ await transport.handleRequest(req, res, parsedBody);
68
+ } catch {
69
+ if (!res.headersSent) {
70
+ writeJson(res, 500, { error: "MCP request failed." });
71
+ }
72
+ } finally {
73
+ await transport.close();
74
+ await mcp.close();
75
+ }
76
+ }
77
+ function loadRemoteMcpConfig(env) {
78
+ const host = env["EVERFUR_MCP_HTTP_HOST"]?.trim() || DEFAULT_HOST;
79
+ const accessToken = requireValue(env, "EVERFUR_MCP_HTTP_ACCESS_TOKEN");
80
+ if (accessToken.length < MIN_ACCESS_TOKEN_LENGTH) {
81
+ throw new Error(`EVERFUR_MCP_HTTP_ACCESS_TOKEN must be at least ${MIN_ACCESS_TOKEN_LENGTH} characters.`);
82
+ }
83
+ const configuredHosts = parseCsv(env["EVERFUR_MCP_HTTP_ALLOWED_HOSTS"], "EVERFUR_MCP_HTTP_ALLOWED_HOSTS");
84
+ if (!isLoopbackHost(host) && configuredHosts.size === 0) {
85
+ throw new Error("EVERFUR_MCP_HTTP_ALLOWED_HOSTS is required when EVERFUR_MCP_HTTP_HOST is not loopback.");
86
+ }
87
+ const allowedHosts = configuredHosts.size > 0 ? configuredHosts : /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
88
+ return {
89
+ env,
90
+ host,
91
+ port: parsePort(env["EVERFUR_MCP_HTTP_PORT"]),
92
+ path: parsePath(env["EVERFUR_MCP_HTTP_PATH"]),
93
+ accessToken,
94
+ allowedHosts: new Set([...allowedHosts].map((value) => value.toLowerCase())),
95
+ allowedOrigins: /* @__PURE__ */ new Set([...parseCsv(env["EVERFUR_MCP_HTTP_ALLOWED_ORIGINS"], "EVERFUR_MCP_HTTP_ALLOWED_ORIGINS")])
96
+ };
97
+ }
98
+ function requestIsAllowed(req, config) {
99
+ const requestUrl = new URL(req.url ?? "/", "http://mcp.invalid");
100
+ if (requestUrl.pathname !== config.path) {
101
+ return { ok: false, status: 404, message: "Not found." };
102
+ }
103
+ if (req.method !== "POST") {
104
+ return { ok: false, status: 405, message: "Method not allowed.", headers: { allow: "POST" } };
105
+ }
106
+ const host = hostnameOf(req.headers.host);
107
+ if (host === null || !config.allowedHosts.has(host)) {
108
+ return { ok: false, status: 421, message: "Host is not allowed." };
109
+ }
110
+ const origin = req.headers.origin;
111
+ if (origin !== void 0 && !config.allowedOrigins.has(origin)) {
112
+ return { ok: false, status: 403, message: "Origin is not allowed." };
113
+ }
114
+ const authorization = req.headers.authorization;
115
+ const token = authorization?.match(/^Bearer ([^\s]+)$/i)?.[1];
116
+ if (token === void 0 || !safeEquals(token, config.accessToken)) {
117
+ return { ok: false, status: 401, message: "Authentication required." };
118
+ }
119
+ return { ok: true };
120
+ }
121
+ function createRemoteMcpRequestListener(config) {
122
+ return async (req, res) => {
123
+ const allowed = requestIsAllowed(req, config);
124
+ if (allowed.ok === false) {
125
+ if (allowed.status === 401) res.setHeader("www-authenticate", "Bearer");
126
+ writeJson(res, allowed.status, { error: allowed.message }, allowed.headers ?? {});
127
+ return;
128
+ }
129
+ await serveStatelessMcp(req, res, buildServer({ env: config.env }));
130
+ };
131
+ }
132
+ function listen(server, port, host) {
133
+ return new Promise((resolve, reject) => {
134
+ server.once("error", reject);
135
+ server.listen(port, host, () => {
136
+ server.off("error", reject);
137
+ resolve();
138
+ });
139
+ });
140
+ }
141
+ function close(server) {
142
+ return new Promise((resolve, reject) => {
143
+ server.close((error) => error ? reject(error) : resolve());
144
+ });
145
+ }
146
+ async function startRemoteMcpServer(config) {
147
+ const server = createServer(createRemoteMcpRequestListener(config));
148
+ await listen(server, config.port, config.host);
149
+ const address = server.address();
150
+ if (address === null || typeof address === "string") {
151
+ await close(server);
152
+ throw new Error("The HTTP MCP server did not bind a TCP address.");
153
+ }
154
+ const hostname = address.family === "IPv6" ? `[${address.address}]` : address.address;
155
+ return {
156
+ url: new URL(`http://${hostname}:${address.port}${config.path}`),
157
+ close: () => close(server)
158
+ };
159
+ }
160
+
161
+ export { close, hostnameOf, listen, loadRemoteMcpConfig, serveStatelessMcp, startRemoteMcpServer, writeJson };