@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.
Files changed (51) hide show
  1. package/README.md +96 -0
  2. package/dist/api/client.d.ts +31 -0
  3. package/dist/api/client.d.ts.map +1 -0
  4. package/dist/api/client.js +150 -0
  5. package/dist/api/client.js.map +1 -0
  6. package/dist/api/models.d.ts +19 -0
  7. package/dist/api/models.d.ts.map +1 -0
  8. package/dist/api/models.js +75 -0
  9. package/dist/api/models.js.map +1 -0
  10. package/dist/api/types.d.ts +40 -0
  11. package/dist/api/types.d.ts.map +1 -0
  12. package/dist/api/types.js +2 -0
  13. package/dist/api/types.js.map +1 -0
  14. package/dist/apps/viewer.html +51 -0
  15. package/dist/lib/create-main.d.ts +9 -0
  16. package/dist/lib/create-main.d.ts.map +1 -0
  17. package/dist/lib/create-main.js +27 -0
  18. package/dist/lib/create-main.js.map +1 -0
  19. package/dist/lib/errors.d.ts +16 -0
  20. package/dist/lib/errors.d.ts.map +1 -0
  21. package/dist/lib/errors.js +17 -0
  22. package/dist/lib/errors.js.map +1 -0
  23. package/dist/lib/fetch-url.d.ts +18 -0
  24. package/dist/lib/fetch-url.d.ts.map +1 -0
  25. package/dist/lib/fetch-url.js +239 -0
  26. package/dist/lib/fetch-url.js.map +1 -0
  27. package/dist/lib/http-server.d.ts +18 -0
  28. package/dist/lib/http-server.d.ts.map +1 -0
  29. package/dist/lib/http-server.js +225 -0
  30. package/dist/lib/http-server.js.map +1 -0
  31. package/dist/lib/path-security.d.ts +21 -0
  32. package/dist/lib/path-security.d.ts.map +1 -0
  33. package/dist/lib/path-security.js +210 -0
  34. package/dist/lib/path-security.js.map +1 -0
  35. package/dist/main.d.ts +3 -0
  36. package/dist/main.d.ts.map +1 -0
  37. package/dist/main.js +8 -0
  38. package/dist/main.js.map +1 -0
  39. package/dist/server.d.ts +3 -0
  40. package/dist/server.d.ts.map +1 -0
  41. package/dist/server.js +108 -0
  42. package/dist/server.js.map +1 -0
  43. package/dist/tools/compare.d.ts +9 -0
  44. package/dist/tools/compare.d.ts.map +1 -0
  45. package/dist/tools/compare.js +133 -0
  46. package/dist/tools/compare.js.map +1 -0
  47. package/dist/tools/enhance.d.ts +22 -0
  48. package/dist/tools/enhance.d.ts.map +1 -0
  49. package/dist/tools/enhance.js +175 -0
  50. package/dist/tools/enhance.js.map +1 -0
  51. 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":"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"}
@@ -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"}