@topazlabs/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/api/client.d.ts +31 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +150 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/models.d.ts +19 -0
- package/dist/api/models.d.ts.map +1 -0
- package/dist/api/models.js +75 -0
- package/dist/api/models.js.map +1 -0
- package/dist/api/types.d.ts +40 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/apps/viewer.html +51 -0
- package/dist/lib/create-main.d.ts +9 -0
- package/dist/lib/create-main.d.ts.map +1 -0
- package/dist/lib/create-main.js +27 -0
- package/dist/lib/create-main.js.map +1 -0
- package/dist/lib/errors.d.ts +16 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +17 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/fetch-url.d.ts +18 -0
- package/dist/lib/fetch-url.d.ts.map +1 -0
- package/dist/lib/fetch-url.js +239 -0
- package/dist/lib/fetch-url.js.map +1 -0
- package/dist/lib/http-server.d.ts +18 -0
- package/dist/lib/http-server.d.ts.map +1 -0
- package/dist/lib/http-server.js +225 -0
- package/dist/lib/http-server.js.map +1 -0
- package/dist/lib/path-security.d.ts +21 -0
- package/dist/lib/path-security.d.ts.map +1 -0
- package/dist/lib/path-security.js +210 -0
- package/dist/lib/path-security.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +108 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/compare.d.ts +9 -0
- package/dist/tools/compare.d.ts.map +1 -0
- package/dist/tools/compare.js +133 -0
- package/dist/tools/compare.js.map +1 -0
- package/dist/tools/enhance.d.ts +22 -0
- package/dist/tools/enhance.d.ts.map +1 -0
- package/dist/tools/enhance.js +175 -0
- package/dist/tools/enhance.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure URL fetching with SSRF protection.
|
|
3
|
+
*
|
|
4
|
+
* - Only allows http:// and https:// schemes
|
|
5
|
+
* - Resolves DNS before connecting and blocks private/internal IPs
|
|
6
|
+
* - Enforces response size limits
|
|
7
|
+
* - Adds request timeout
|
|
8
|
+
* - Re-checks each redirect destination
|
|
9
|
+
*/
|
|
10
|
+
import * as https from "node:https";
|
|
11
|
+
import * as http from "node:http";
|
|
12
|
+
import * as net from "node:net";
|
|
13
|
+
import { lookup } from "node:dns/promises";
|
|
14
|
+
const DEFAULT_MAX_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 30_000; // 30 seconds
|
|
16
|
+
const MAX_REDIRECTS = 5;
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Private IP detection
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function isPrivateIPv4(ip) {
|
|
21
|
+
const parts = ip.split(".").map(Number);
|
|
22
|
+
if (parts.length !== 4)
|
|
23
|
+
return false;
|
|
24
|
+
// 10.0.0.0/8
|
|
25
|
+
if (parts[0] === 10)
|
|
26
|
+
return true;
|
|
27
|
+
// 172.16.0.0/12
|
|
28
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
|
29
|
+
return true;
|
|
30
|
+
// 192.168.0.0/16
|
|
31
|
+
if (parts[0] === 192 && parts[1] === 168)
|
|
32
|
+
return true;
|
|
33
|
+
// 127.0.0.0/8 (loopback)
|
|
34
|
+
if (parts[0] === 127)
|
|
35
|
+
return true;
|
|
36
|
+
// 169.254.0.0/16 (link-local)
|
|
37
|
+
if (parts[0] === 169 && parts[1] === 254)
|
|
38
|
+
return true;
|
|
39
|
+
// 0.0.0.0/8
|
|
40
|
+
if (parts[0] === 0)
|
|
41
|
+
return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
function isPrivateIPv6(ip) {
|
|
45
|
+
const lower = ip.toLowerCase();
|
|
46
|
+
// ::1 loopback
|
|
47
|
+
if (lower === "::1")
|
|
48
|
+
return true;
|
|
49
|
+
// :: unspecified
|
|
50
|
+
if (lower === "::")
|
|
51
|
+
return true;
|
|
52
|
+
// fc00::/7 (unique local) — starts with fc or fd
|
|
53
|
+
if (lower.startsWith("fc") || lower.startsWith("fd"))
|
|
54
|
+
return true;
|
|
55
|
+
// fe80::/10 (link-local)
|
|
56
|
+
if (lower.startsWith("fe80"))
|
|
57
|
+
return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function isPrivateIP(address) {
|
|
61
|
+
// IPv4-mapped IPv6 (e.g., ::ffff:127.0.0.1)
|
|
62
|
+
if (address.startsWith("::ffff:")) {
|
|
63
|
+
const ipv4Part = address.slice(7);
|
|
64
|
+
if (net.isIPv4(ipv4Part))
|
|
65
|
+
return isPrivateIPv4(ipv4Part);
|
|
66
|
+
}
|
|
67
|
+
if (net.isIPv4(address))
|
|
68
|
+
return isPrivateIPv4(address);
|
|
69
|
+
if (net.isIPv6(address))
|
|
70
|
+
return isPrivateIPv6(address);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// DNS + SSRF validation
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
export async function validateUrl(url) {
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = new URL(url);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
83
|
+
}
|
|
84
|
+
// Only allow http/https
|
|
85
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
86
|
+
throw new Error(`Invalid URL scheme: ${parsed.protocol} (only http: and https: allowed)`);
|
|
87
|
+
}
|
|
88
|
+
const hostname = parsed.hostname;
|
|
89
|
+
// Block obvious private hostnames
|
|
90
|
+
if (hostname === "localhost" ||
|
|
91
|
+
hostname.endsWith(".local") ||
|
|
92
|
+
hostname.endsWith(".internal")) {
|
|
93
|
+
throw new Error("URL points to private/internal network");
|
|
94
|
+
}
|
|
95
|
+
// If it's a raw IP, check directly
|
|
96
|
+
if (net.isIP(hostname)) {
|
|
97
|
+
if (isPrivateIP(hostname)) {
|
|
98
|
+
throw new Error("URL points to private/internal network");
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Resolve DNS and check all addresses
|
|
103
|
+
try {
|
|
104
|
+
const results = await lookup(hostname, { all: true });
|
|
105
|
+
for (const { address } of results) {
|
|
106
|
+
if (isPrivateIP(address)) {
|
|
107
|
+
throw new Error("URL resolves to private/internal network address");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
113
|
+
if (msg.includes("private/internal"))
|
|
114
|
+
throw e;
|
|
115
|
+
throw new Error(`Could not resolve hostname: ${hostname}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve DNS for a hostname and return a validated (non-private) IP address.
|
|
120
|
+
* Throws if the hostname resolves to a private/internal IP.
|
|
121
|
+
*/
|
|
122
|
+
async function resolveAndValidate(hostname) {
|
|
123
|
+
// Raw IPs don't need DNS resolution
|
|
124
|
+
if (net.isIP(hostname)) {
|
|
125
|
+
if (isPrivateIP(hostname)) {
|
|
126
|
+
throw new Error("URL points to private/internal network");
|
|
127
|
+
}
|
|
128
|
+
return undefined; // Caller should use hostname as-is
|
|
129
|
+
}
|
|
130
|
+
const results = await lookup(hostname, { all: true });
|
|
131
|
+
for (const { address } of results) {
|
|
132
|
+
if (isPrivateIP(address)) {
|
|
133
|
+
throw new Error("URL resolves to private/internal network address");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Return the first valid address to pin the connection to
|
|
137
|
+
return results[0]?.address;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Fetch a URL with SSRF protection, size limits, timeout, and redirect safety.
|
|
141
|
+
* Connects directly to the resolved IP to prevent DNS rebinding (TOCTOU) attacks.
|
|
142
|
+
*/
|
|
143
|
+
export async function fetchUrl(url, options = {}, _redirectCount = 0) {
|
|
144
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
145
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
146
|
+
if (_redirectCount >= MAX_REDIRECTS) {
|
|
147
|
+
throw new Error("Too many redirects (max 5)");
|
|
148
|
+
}
|
|
149
|
+
// Validate before every request (including redirects)
|
|
150
|
+
await validateUrl(url);
|
|
151
|
+
const parsed = new URL(url);
|
|
152
|
+
const mod = parsed.protocol === "https:" ? https : http;
|
|
153
|
+
// Resolve DNS and pin the IP to prevent TOCTOU / DNS rebinding attacks
|
|
154
|
+
const resolvedIP = await resolveAndValidate(parsed.hostname);
|
|
155
|
+
// Build request options that connect to the pinned IP but preserve the
|
|
156
|
+
// original hostname for Host header and TLS SNI.
|
|
157
|
+
const port = parsed.port
|
|
158
|
+
? Number(parsed.port)
|
|
159
|
+
: parsed.protocol === "https:"
|
|
160
|
+
? 443
|
|
161
|
+
: 80;
|
|
162
|
+
const reqOptions = {
|
|
163
|
+
method: "GET",
|
|
164
|
+
host: resolvedIP ?? parsed.hostname, // Connect to the pinned IP
|
|
165
|
+
port,
|
|
166
|
+
path: parsed.pathname + parsed.search,
|
|
167
|
+
headers: {
|
|
168
|
+
Host: parsed.host, // Include port if non-default
|
|
169
|
+
},
|
|
170
|
+
// For HTTPS: set servername for correct SNI
|
|
171
|
+
...(parsed.protocol === "https:" && resolvedIP
|
|
172
|
+
? { servername: parsed.hostname }
|
|
173
|
+
: {}),
|
|
174
|
+
};
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const timer = setTimeout(() => {
|
|
177
|
+
req.destroy();
|
|
178
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
179
|
+
}, timeoutMs);
|
|
180
|
+
const req = mod.request(reqOptions, (res) => {
|
|
181
|
+
// Redirect handling — re-validate the destination
|
|
182
|
+
if (res.statusCode &&
|
|
183
|
+
res.statusCode >= 300 &&
|
|
184
|
+
res.statusCode < 400 &&
|
|
185
|
+
res.headers.location) {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
res.resume();
|
|
188
|
+
const redirectUrl = new URL(res.headers.location, url).toString();
|
|
189
|
+
fetchUrl(redirectUrl, options, _redirectCount + 1).then(resolve, reject);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
reject(new Error(`HTTP error ${res.statusCode} fetching ${url}`));
|
|
195
|
+
res.resume();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const chunks = [];
|
|
199
|
+
let totalSize = 0;
|
|
200
|
+
res.on("data", (chunk) => {
|
|
201
|
+
totalSize += chunk.length;
|
|
202
|
+
if (totalSize > maxBytes) {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
res.destroy();
|
|
205
|
+
reject(new Error(`Response too large (exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit)`));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
chunks.push(chunk);
|
|
209
|
+
});
|
|
210
|
+
res.on("end", () => {
|
|
211
|
+
clearTimeout(timer);
|
|
212
|
+
resolve(Buffer.concat(chunks));
|
|
213
|
+
});
|
|
214
|
+
res.on("error", (err) => {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
reject(err);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
req.on("error", (err) => {
|
|
220
|
+
clearTimeout(timer);
|
|
221
|
+
reject(err);
|
|
222
|
+
});
|
|
223
|
+
req.end();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Load an image from a URL or local path.
|
|
228
|
+
* URLs go through SSRF-safe fetchUrl; paths are validated by the caller.
|
|
229
|
+
*/
|
|
230
|
+
export async function loadImage(source, options) {
|
|
231
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
232
|
+
return fetchUrl(source, options);
|
|
233
|
+
}
|
|
234
|
+
// For local paths, the caller must validate with validateReadPath first
|
|
235
|
+
const fs = await import("node:fs");
|
|
236
|
+
const pathMod = await import("node:path");
|
|
237
|
+
return fs.promises.readFile(pathMod.resolve(source));
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=fetch-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-url.js","sourceRoot":"","sources":["../../src/lib/fetch-url.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP transport with security hardening.
|
|
3
|
+
*
|
|
4
|
+
* - Bearer token auth (MCP_AUTH_TOKEN env var, required)
|
|
5
|
+
* - CORS restriction (MCP_CORS_ORIGIN env var, default: deny)
|
|
6
|
+
* - Rate limiting (100 req/min per IP)
|
|
7
|
+
* - Request body size limit (50 MB)
|
|
8
|
+
* - Session TTL (30 min inactivity) + cleanup + max sessions (100)
|
|
9
|
+
* - Graceful shutdown on SIGTERM/SIGINT
|
|
10
|
+
*/
|
|
11
|
+
import { type Server } from "node:http";
|
|
12
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
export interface HttpServerOptions {
|
|
14
|
+
createServer: () => McpServer;
|
|
15
|
+
port?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function startHttpServer(options: HttpServerOptions): Promise<Server>;
|
|
18
|
+
//# sourceMappingURL=http-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/lib/http-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAuDzE,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,SAAS,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,CAsOjB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP transport with security hardening.
|
|
3
|
+
*
|
|
4
|
+
* - Bearer token auth (MCP_AUTH_TOKEN env var, required)
|
|
5
|
+
* - CORS restriction (MCP_CORS_ORIGIN env var, default: deny)
|
|
6
|
+
* - Rate limiting (100 req/min per IP)
|
|
7
|
+
* - Request body size limit (50 MB)
|
|
8
|
+
* - Session TTL (30 min inactivity) + cleanup + max sessions (100)
|
|
9
|
+
* - Graceful shutdown on SIGTERM/SIGINT
|
|
10
|
+
*/
|
|
11
|
+
import { createServer as createNodeHttpServer, } from "node:http";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
|
+
const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
15
|
+
const RATE_LIMIT_MAX = 100;
|
|
16
|
+
const rateLimitMap = new Map();
|
|
17
|
+
function checkRateLimit(ip) {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const entry = rateLimitMap.get(ip);
|
|
20
|
+
if (!entry || now > entry.resetAt) {
|
|
21
|
+
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
entry.count++;
|
|
25
|
+
return entry.count <= RATE_LIMIT_MAX;
|
|
26
|
+
}
|
|
27
|
+
// Background cleanup for stale rate-limit entries
|
|
28
|
+
const rateLimitCleanup = setInterval(() => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
for (const [ip, entry] of rateLimitMap) {
|
|
31
|
+
if (now > entry.resetAt)
|
|
32
|
+
rateLimitMap.delete(ip);
|
|
33
|
+
}
|
|
34
|
+
}, RATE_LIMIT_WINDOW_MS);
|
|
35
|
+
rateLimitCleanup.unref();
|
|
36
|
+
const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
37
|
+
const SESSION_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
38
|
+
const MAX_SESSIONS = 100;
|
|
39
|
+
const MAX_BODY_SIZE = 50 * 1024 * 1024; // 50 MB
|
|
40
|
+
export async function startHttpServer(options) {
|
|
41
|
+
const port = options.port ?? parseInt(process.env.PORT || "3001", 10);
|
|
42
|
+
// Fail secure: require auth token
|
|
43
|
+
const authToken = process.env.MCP_AUTH_TOKEN;
|
|
44
|
+
if (!authToken) {
|
|
45
|
+
console.error("FATAL: MCP_AUTH_TOKEN environment variable is required for HTTP mode.");
|
|
46
|
+
console.error("Set it before starting: MCP_AUTH_TOKEN=<token> PORT=3001 node dist/main.js");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const corsOrigin = process.env.MCP_CORS_ORIGIN || "";
|
|
50
|
+
const sessions = new Map();
|
|
51
|
+
// Periodic session cleanup
|
|
52
|
+
const sessionCleanup = setInterval(() => {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [id, entry] of sessions) {
|
|
55
|
+
if (now - entry.lastActivity > SESSION_TTL_MS) {
|
|
56
|
+
try {
|
|
57
|
+
entry.transport.close();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* already closed */
|
|
61
|
+
}
|
|
62
|
+
sessions.delete(id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, SESSION_CLEANUP_INTERVAL_MS);
|
|
66
|
+
function getClientIp(req) {
|
|
67
|
+
if (process.env.TOPAZ_TRUST_PROXY) {
|
|
68
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
69
|
+
if (typeof forwarded === "string")
|
|
70
|
+
return forwarded.split(",")[0].trim();
|
|
71
|
+
}
|
|
72
|
+
return req.socket.remoteAddress ?? "unknown";
|
|
73
|
+
}
|
|
74
|
+
function setCorsHeaders(res) {
|
|
75
|
+
if (corsOrigin) {
|
|
76
|
+
res.setHeader("Access-Control-Allow-Origin", corsOrigin);
|
|
77
|
+
}
|
|
78
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
79
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, Authorization");
|
|
80
|
+
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
81
|
+
}
|
|
82
|
+
function checkAuth(req, res) {
|
|
83
|
+
const header = req.headers.authorization;
|
|
84
|
+
if (!header || header !== `Bearer ${authToken}`) {
|
|
85
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
86
|
+
res.end(JSON.stringify({ error: "Unauthorized: valid Bearer token required" }));
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
function enforceBodyLimit(req, res) {
|
|
92
|
+
// Quick reject if Content-Length is declared and too large
|
|
93
|
+
const cl = req.headers["content-length"];
|
|
94
|
+
if (cl !== undefined) {
|
|
95
|
+
const size = parseInt(cl, 10);
|
|
96
|
+
if (size > MAX_BODY_SIZE) {
|
|
97
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
98
|
+
res.end(JSON.stringify({
|
|
99
|
+
error: `Request body too large (max ${MAX_BODY_SIZE / (1024 * 1024)}MB)`,
|
|
100
|
+
}));
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Also enforce during streaming (handles chunked transfer encoding)
|
|
105
|
+
let received = 0;
|
|
106
|
+
req.on("data", (chunk) => {
|
|
107
|
+
received += chunk.length;
|
|
108
|
+
if (received > MAX_BODY_SIZE) {
|
|
109
|
+
req.destroy();
|
|
110
|
+
if (!res.headersSent) {
|
|
111
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({
|
|
113
|
+
error: `Request body too large (max ${MAX_BODY_SIZE / (1024 * 1024)}MB)`,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
const httpServer = createNodeHttpServer(async (req, res) => {
|
|
121
|
+
setCorsHeaders(res);
|
|
122
|
+
if (req.method === "OPTIONS") {
|
|
123
|
+
res.writeHead(204);
|
|
124
|
+
res.end();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Health check — no auth required
|
|
128
|
+
if (req.url === "/health") {
|
|
129
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
130
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Rate limiting
|
|
134
|
+
const clientIp = getClientIp(req);
|
|
135
|
+
if (!checkRateLimit(clientIp)) {
|
|
136
|
+
res.writeHead(429, { "Content-Type": "application/json" });
|
|
137
|
+
res.end(JSON.stringify({ error: "Too many requests. Try again later." }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Auth
|
|
141
|
+
if (!checkAuth(req, res))
|
|
142
|
+
return;
|
|
143
|
+
// Body size
|
|
144
|
+
if (!enforceBodyLimit(req, res))
|
|
145
|
+
return;
|
|
146
|
+
// MCP endpoint
|
|
147
|
+
if (req.url === "/mcp") {
|
|
148
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
149
|
+
// Existing session
|
|
150
|
+
if (sessionId) {
|
|
151
|
+
const entry = sessions.get(sessionId);
|
|
152
|
+
if (entry) {
|
|
153
|
+
entry.lastActivity = Date.now();
|
|
154
|
+
await entry.transport.handleRequest(req, res);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
158
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// New session
|
|
162
|
+
if (req.method === "POST") {
|
|
163
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
164
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
165
|
+
res.end(JSON.stringify({
|
|
166
|
+
error: "Too many active sessions. Try again later.",
|
|
167
|
+
}));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const transport = new StreamableHTTPServerTransport({
|
|
171
|
+
sessionIdGenerator: () => randomUUID(),
|
|
172
|
+
});
|
|
173
|
+
const server = options.createServer();
|
|
174
|
+
await server.connect(transport);
|
|
175
|
+
transport.onclose = () => {
|
|
176
|
+
if (transport.sessionId) {
|
|
177
|
+
sessions.delete(transport.sessionId);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
await transport.handleRequest(req, res);
|
|
181
|
+
if (transport.sessionId) {
|
|
182
|
+
sessions.set(transport.sessionId, {
|
|
183
|
+
transport,
|
|
184
|
+
lastActivity: Date.now(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
190
|
+
res.end(JSON.stringify({ error: "Bad request: missing session ID" }));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
res.writeHead(404);
|
|
194
|
+
res.end("Not found");
|
|
195
|
+
});
|
|
196
|
+
// Graceful shutdown
|
|
197
|
+
function shutdown() {
|
|
198
|
+
clearInterval(sessionCleanup);
|
|
199
|
+
httpServer.close(() => {
|
|
200
|
+
for (const [, entry] of sessions) {
|
|
201
|
+
try {
|
|
202
|
+
entry.transport.close();
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
/* ok */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
process.exit(0);
|
|
209
|
+
});
|
|
210
|
+
// Force exit after 5s
|
|
211
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
212
|
+
}
|
|
213
|
+
process.on("SIGTERM", shutdown);
|
|
214
|
+
process.on("SIGINT", shutdown);
|
|
215
|
+
httpServer.listen(port, () => {
|
|
216
|
+
const url = `http://localhost:${port}`;
|
|
217
|
+
console.error(`MCP Streamable HTTP server listening on port ${port}`);
|
|
218
|
+
console.error(` Health: ${url}/health`);
|
|
219
|
+
console.error(` MCP: ${url}/mcp`);
|
|
220
|
+
console.error(` Auth: Bearer token required`);
|
|
221
|
+
console.error(` CORS: ${corsOrigin || "(denied)"}`);
|
|
222
|
+
});
|
|
223
|
+
return httpServer;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=http-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/lib/http-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,YAAY,IAAI,oBAAoB,GAIrC,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAYnG,MAAM,oBAAoB,GAAG,MAAM,CAAC;AACpC,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,YAAY,GAAG,IAAI,GAAG,EAA0B,CAAC;AAEvD,SAAS,cAAc,CAAC,EAAU;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEnC,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAClC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,oBAAoB,EAAE,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,OAAO,KAAK,CAAC,KAAK,IAAI,cAAc,CAAC;AACvC,CAAC;AAED,kDAAkD;AAClD,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO;YAAE,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,EAAE,oBAAoB,CAAC,CAAC;AACzB,gBAAgB,CAAC,KAAK,EAAE,CAAC;AAWzB,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACpD,MAAM,2BAA2B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAC/D,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ;AAWhD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA0B;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;IAEtE,kCAAkC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,uEAAuE,CACxE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,4EAA4E,CAC7E,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;IAErD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IAEjD,2BAA2B;IAC3B,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,KAAK,CAAC,YAAY,GAAG,cAAc,EAAE,CAAC;gBAC9C,IAAI,CAAC;oBACH,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,oBAAoB;gBACtB,CAAC;gBACD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAEhC,SAAS,WAAW,CAAC,GAAoB;QACvC,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;YACjD,IAAI,OAAO,SAAS,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3E,CAAC;QACD,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IAC/C,CAAC;IAED,SAAS,cAAc,CAAC,GAAmB;QACzC,IAAI,UAAU,EAAE,CAAC;YACf,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,UAAU,CAAC,CAAC;QAC3D,CAAC;QACD,GAAG,CAAC,SAAS,CACX,8BAA8B,EAC9B,4BAA4B,CAC7B,CAAC;QACF,GAAG,CAAC,SAAS,CACX,8BAA8B,EAC9B,6CAA6C,CAC9C,CAAC;QACF,GAAG,CAAC,SAAS,CAAC,+BAA+B,EAAE,gBAAgB,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,SAAS,CAAC,GAAoB,EAAE,GAAmB;QAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QACzC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,UAAU,SAAS,EAAE,EAAE,CAAC;YAChD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC,CACvE,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,gBAAgB,CAAC,GAAoB,EAAE,GAAmB;QACjE,2DAA2D;QAC3D,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzC,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,IAAI,GAAG,aAAa,EAAE,CAAC;gBACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;oBACb,KAAK,EAAE,+BAA+B,aAAa,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK;iBACzE,CAAC,CACH,CAAC;gBACF,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,oEAAoE;QACpE,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC/B,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;YACzB,IAAI,QAAQ,GAAG,aAAa,EAAE,CAAC;gBAC7B,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;oBACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;wBACb,KAAK,EAAE,+BAA+B,aAAa,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK;qBACzE,CAAC,CACH,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CACrC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE;QAClD,cAAc,CAAC,GAAG,CAAC,CAAC;QAEpB,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,kCAAkC;QAClC,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,gBAAgB;QAChB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CACjE,CAAC;YACF,OAAO;QACT,CAAC;QAED,OAAO;QACP,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,YAAY;QACZ,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAExC,eAAe;QACf,IAAI,GAAG,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC;YACvB,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAuB,CAAC;YAEtE,mBAAmB;YACnB,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACtC,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBAChC,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBAC9C,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,cAAc;YACd,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC1B,IAAI,QAAQ,CAAC,IAAI,IAAI,YAAY,EAAE,CAAC;oBAClC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;wBACb,KAAK,EAAE,4CAA4C;qBACpD,CAAC,CACH,CAAC;oBACF,OAAO;gBACT,CAAC;gBAED,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;oBAClD,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;iBACvC,CAAC,CAAC;gBACH,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;gBACtC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBAEhC,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE;oBACvB,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;wBACxB,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBACvC,CAAC;gBACH,CAAC,CAAC;gBAEF,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAExC,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;oBACxB,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE;wBAChC,SAAS;wBACT,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;qBACzB,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO;YACT,CAAC;YAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC,CAAC;YACtE,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACnB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,CACF,CAAC;IAEF,oBAAoB;IACpB,SAAS,QAAQ;QACf,aAAa,CAAC,cAAc,CAAC,CAAC;QAC9B,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE;YACpB,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,QAAQ;gBACV,CAAC;YACH,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,sBAAsB;QACtB,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE/B,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QAC3B,MAAM,GAAG,GAAG,oBAAoB,IAAI,EAAE,CAAC;QACvC,OAAO,CAAC,KAAK,CAAC,gDAAgD,IAAI,EAAE,CAAC,CAAC;QACtE,OAAO,CAAC,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACjD,OAAO,CAAC,KAAK,CAAC,aAAa,UAAU,IAAI,UAAU,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type TransportMode = "stdio" | "http";
|
|
2
|
+
export interface PathSecurityConfig {
|
|
3
|
+
mode: TransportMode;
|
|
4
|
+
allowedDirs?: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Configure path security. Call once at startup before any tool handlers run.
|
|
8
|
+
* Default: stdio mode (CWD + temp allowed).
|
|
9
|
+
*/
|
|
10
|
+
export declare function configurePathSecurity(options: PathSecurityConfig): void;
|
|
11
|
+
/**
|
|
12
|
+
* Validate a file path for reading. Returns the resolved absolute path.
|
|
13
|
+
* Throws if the path is outside allowed directories or in a sensitive location.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateReadPath(inputPath: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a file path for writing. Returns the resolved absolute path.
|
|
18
|
+
* Throws if the path is outside allowed directories or in a sensitive location.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateWritePath(inputPath: string): string;
|
|
21
|
+
//# sourceMappingURL=path-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AA+MD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAEvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|