@sylphlab/mcp-net-core
Version:
Core logic for MCP network tools
515 lines (502 loc) • 20.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
GetInterfacesToolInputSchema: () => GetInterfacesToolInputSchema,
GetPublicIpToolInputSchema: () => GetPublicIpToolInputSchema,
downloadTool: () => downloadTool,
downloadToolInputSchema: () => downloadToolInputSchema,
fetchTool: () => fetchTool,
getInterfacesTool: () => getInterfacesTool,
getPublicIpTool: () => getPublicIpTool
});
module.exports = __toCommonJS(index_exports);
// src/tools/getPublicIpTool.ts
var import_mcp_core = require("@sylphlab/mcp-core");
var import_mcp_core2 = require("@sylphlab/mcp-core");
var import_zod2 = require("zod");
// src/tools/getPublicIpTool.schema.ts
var import_zod = require("zod");
var GetPublicIpToolInputSchema = import_zod.z.object({
id: import_zod.z.string().optional()
// Keep id for correlation if used in batch by server
});
// src/tools/getPublicIpTool.ts
var GetPublicIpResultSchema = import_zod2.z.object({
id: import_zod2.z.string().optional(),
success: import_zod2.z.boolean(),
ip: import_zod2.z.string().optional(),
error: import_zod2.z.string().optional(),
suggestion: import_zod2.z.string().optional()
});
var GetPublicIpOutputSchema = import_zod2.z.array(GetPublicIpResultSchema);
async function fetchPublicIp() {
try {
const response = await fetch("https://ipinfo.io/json");
if (!response.ok) throw new Error(`ipinfo.io HTTP error! status: ${response.status}`);
const data = await response.json();
if (typeof data?.ip !== "string")
throw new Error("IP address not found or invalid in response from ipinfo.io");
return { ip: data.ip, error: null };
} catch (e) {
const errorMsg = `Failed to fetch public IP from ipinfo.io: ${e instanceof Error ? e.message : String(e)}`;
try {
const fallbackResponse = await fetch("https://api.ipify.org?format=json");
if (!fallbackResponse.ok)
throw new Error(`api.ipify.org HTTP error! status: ${fallbackResponse.status}`);
const fallbackData = await fallbackResponse.json();
if (typeof fallbackData?.ip !== "string")
throw new Error("IP address not found or invalid in fallback response from api.ipify.org");
return { ip: fallbackData.ip, error: null };
} catch (fallbackError) {
const fallbackErrorMsg = `Fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`;
return { ip: null, error: `${errorMsg}. ${fallbackErrorMsg}` };
}
}
}
var getPublicIpTool = (0, import_mcp_core.defineTool)({
name: "getPublicIp",
description: "Retrieves the public IP address of the machine running the MCP server.",
inputSchema: GetPublicIpToolInputSchema,
outputSchema: GetPublicIpOutputSchema,
// Use the array schema
execute: async (input, _options) => {
const parsed = GetPublicIpToolInputSchema.safeParse(input);
if (!parsed.success) {
const errorMessages = Object.entries(parsed.error.flatten().fieldErrors).map(([field, messages]) => `${field}: ${messages.join(", ")}`).join("; ");
throw new Error(`Input validation failed: ${errorMessages}`);
}
const { id } = parsed.data;
const results = [];
let ip;
let error;
let suggestion;
let success = false;
try {
const publicIpInfo = await fetchPublicIp();
if (publicIpInfo.error) {
throw new Error(publicIpInfo.error);
}
if (publicIpInfo.ip) {
ip = publicIpInfo.ip;
success = true;
} else {
throw new Error("Public IP could not be determined despite no specific error reported.");
}
} catch (e) {
success = false;
error = e instanceof Error ? e.message : "Unknown error retrieving public IP";
suggestion = "Check network connectivity and reachability of public IP services (ipinfo.io, api.ipify.org).";
ip = void 0;
}
results.push({
id,
success,
ip,
error,
suggestion
});
return [(0, import_mcp_core2.jsonPart)(results, GetPublicIpOutputSchema)];
}
});
// src/tools/getInterfacesTool.ts
var import_node_os = __toESM(require("os"), 1);
var import_mcp_core3 = require("@sylphlab/mcp-core");
var import_mcp_core4 = require("@sylphlab/mcp-core");
var import_zod4 = require("zod");
// src/tools/getInterfacesTool.schema.ts
var import_zod3 = require("zod");
var GetInterfacesToolInputSchema = import_zod3.z.object({
id: import_zod3.z.string().optional()
// Keep id for correlation if used in batch by server
});
// src/tools/getInterfacesTool.ts
var GetInterfacesResultSchema = import_zod4.z.object({
id: import_zod4.z.string().optional(),
success: import_zod4.z.boolean(),
// Using z.custom for complex os types, refine if needed
result: import_zod4.z.custom().optional(),
error: import_zod4.z.string().optional(),
suggestion: import_zod4.z.string().optional()
});
var GetInterfacesOutputSchema = import_zod4.z.array(GetInterfacesResultSchema);
var getInterfacesTool = (0, import_mcp_core3.defineTool)({
name: "getInterfaces",
description: "Retrieves details about the network interfaces on the machine.",
inputSchema: GetInterfacesToolInputSchema,
outputSchema: GetInterfacesOutputSchema,
// Use the array schema
execute: async (input, _options) => {
const parsed = GetInterfacesToolInputSchema.safeParse(input);
if (!parsed.success) {
const errorMessages = Object.entries(parsed.error.flatten().fieldErrors).map(([field, messages]) => `${field}: ${messages.join(", ")}`).join("; ");
throw new Error(`Input validation failed: ${errorMessages}`);
}
const { id } = parsed.data;
const results = [];
let interfaces;
let error;
let suggestion;
let success = false;
try {
interfaces = import_node_os.default.networkInterfaces();
if (!interfaces || Object.keys(interfaces).length === 0) {
throw new Error("No network interfaces found on the system.");
}
success = true;
suggestion = "Result contains local network interface details.";
} catch (e) {
success = false;
error = e instanceof Error ? e.message : "Unknown error retrieving network interfaces";
suggestion = "Check system permissions or if network interfaces are available.";
interfaces = void 0;
}
results.push({
id,
success,
result: interfaces,
error,
suggestion
});
return [(0, import_mcp_core4.jsonPart)(results, GetInterfacesOutputSchema)];
}
});
// src/tools/downloadTool.ts
var import_mcp_core5 = require("@sylphlab/mcp-core");
var import_mcp_core6 = require("@sylphlab/mcp-core");
var import_zod6 = require("zod");
// src/tools/downloadTool.schema.ts
var import_zod5 = require("zod");
var DownloadItemSchema = import_zod5.z.object({
id: import_zod5.z.string().optional(),
url: import_zod5.z.string().url("Invalid URL format."),
destinationPath: import_zod5.z.string().min(1, "Destination path cannot be empty."),
overwrite: import_zod5.z.boolean().default(false)
});
var downloadToolInputSchema = import_zod5.z.object({
items: import_zod5.z.array(DownloadItemSchema).min(1, "At least one download item is required.")
});
// src/tools/downloadTool.ts
var fs = __toESM(require("fs"), 1);
var fsp = __toESM(require("fs/promises"), 1);
var https = __toESM(require("https"), 1);
var path = __toESM(require("path"), 1);
var import_promises = require("stream/promises");
var DownloadResultItemSchema = import_zod6.z.object({
id: import_zod6.z.string().optional(),
path: import_zod6.z.string(),
success: import_zod6.z.boolean(),
message: import_zod6.z.string().optional(),
error: import_zod6.z.string().optional(),
suggestion: import_zod6.z.string().optional()
});
var DownloadToolOutputSchema = import_zod6.z.array(DownloadResultItemSchema);
async function processSingleDownload(item, options) {
const { id, url, destinationPath, overwrite } = item;
const { workspaceRoot, allowOutsideWorkspace } = options;
let absoluteDestPath;
try {
const validationResult = (0, import_mcp_core6.validateAndResolvePath)(
destinationPath,
workspaceRoot,
allowOutsideWorkspace
);
if (typeof validationResult !== "string") {
const error = validationResult?.error ?? "Unknown path validation error";
const suggestion = validationResult?.suggestion ?? "Review path and workspace settings.";
throw new Error(`Path validation failed: ${error} ${suggestion}`);
}
absoluteDestPath = validationResult;
const destDir = path.dirname(absoluteDestPath);
await fsp.mkdir(destDir, { recursive: true });
try {
await fsp.access(absoluteDestPath);
if (!overwrite) {
throw new Error(
`File already exists at '${destinationPath}'. Use overwrite: true to replace.`
);
}
await fsp.unlink(absoluteDestPath);
} catch (error) {
const isEnoent = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
if (!isEnoent) {
throw error;
}
}
const response = await new Promise((resolve, reject) => {
let redirectCount = 0;
const maxRedirects = 5;
const makeRequest = (currentUrl) => {
const request = https.get(currentUrl, (res) => {
const statusCode = res.statusCode ?? 0;
if (statusCode >= 300 && statusCode < 400 && res.headers.location) {
redirectCount++;
if (redirectCount > maxRedirects) {
reject(new Error("Exceeded maximum redirects (5)."));
res.resume();
return;
}
res.resume();
makeRequest(res.headers.location);
return;
}
if (statusCode < 200 || statusCode >= 300) {
let errorBody = "";
res.on("data", (chunk) => {
if (errorBody.length < 500) errorBody += chunk.toString();
});
res.on("end", () => {
reject(
new Error(
`Download failed. Status Code: ${statusCode}. ${errorBody.substring(0, 100)}`
)
);
});
res.resume();
return;
}
resolve(res);
});
request.on("error", (err) => {
reject(new Error(`Network request failed: ${err.message}`));
});
request.setTimeout(3e4, () => {
request.destroy(new Error("Request timed out after 30 seconds"));
});
request.end();
};
makeRequest(url);
});
const fileStream = fs.createWriteStream(absoluteDestPath);
try {
await (0, import_promises.pipeline)(response, fileStream);
} catch (pipeError) {
throw new Error(
`File write failed: ${pipeError instanceof Error ? pipeError.message : String(pipeError)}`
);
}
const successMsg = `Successfully downloaded '${url}' to '${destinationPath}'.`;
return { id, path: destinationPath, success: true, message: successMsg };
} catch (error) {
const errorMsg = `Download failed for item ${id ?? url}: ${error instanceof Error ? error.message : String(error)}`;
if (absoluteDestPath) {
try {
await fsp.unlink(absoluteDestPath);
} catch (cleanupError) {
if (cleanupError && typeof cleanupError === "object" && "code" in cleanupError && cleanupError.code !== "ENOENT") {
}
}
}
let suggestion;
if (error instanceof Error) {
if (error.message?.includes("ENOTFOUND") || error.message?.includes("ECONNREFUSED") || error.message?.includes("Network request failed") || error.message?.includes("timed out")) {
suggestion = "Check the URL and network connectivity.";
} else if (error.message?.includes("EACCES")) {
suggestion = "Check file system write permissions for the destination directory.";
} else if (error.message?.includes("File already exists")) {
suggestion = "Set overwrite: true if you want to replace the existing file.";
} else if (error.message?.includes("Path validation failed")) {
suggestion = error.message.split("Path validation failed: ")[1]?.split("Suggestion: ")[1] ?? "Check path validity and workspace settings.";
} else if (error.message?.includes("File write failed")) {
suggestion = "Check disk space and file system permissions.";
} else {
suggestion = "Review the error message and input parameters.";
}
}
return {
id,
path: destinationPath,
success: false,
error: errorMsg,
// message: errorMsg, // Keep message for success only
suggestion
};
}
}
var downloadTool = (0, import_mcp_core5.defineTool)({
name: "downloadTool",
description: "Downloads one or more files from URLs to specified paths within the workspace.",
inputSchema: downloadToolInputSchema,
outputSchema: DownloadToolOutputSchema,
// Use the array schema
execute: async (input, options) => {
const parsed = downloadToolInputSchema.safeParse(input);
if (!parsed.success) {
const errorMessages = Object.entries(parsed.error.flatten().fieldErrors).map(([field, messages]) => `${field}: ${messages.join(", ")}`).join("; ");
throw new Error(`Input validation failed: ${errorMessages}`);
}
if (!options?.workspaceRoot) {
throw new Error("Workspace root is not available in options.");
}
const { items } = parsed.data;
const results = [];
for (const item of items) {
const result = await processSingleDownload(item, options);
results.push(result);
}
return [(0, import_mcp_core6.jsonPart)(results, DownloadToolOutputSchema)];
}
});
// src/tools/fetchTool.ts
var import_mcp_core7 = require("@sylphlab/mcp-core");
var import_mcp_core8 = require("@sylphlab/mcp-core");
var import_zod8 = require("zod");
// src/tools/fetchTool.schema.ts
var import_zod7 = require("zod");
var HttpMethodSchema = import_zod7.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
var ResponseTypeSchema = import_zod7.z.enum(["text", "json", "ignore"]);
var FetchItemSchema = import_zod7.z.object({
id: import_zod7.z.string().optional(),
url: import_zod7.z.string().url("Invalid URL format."),
method: HttpMethodSchema.default("GET"),
headers: import_zod7.z.record(import_zod7.z.string()).optional(),
// Record<string, string>
body: import_zod7.z.string().optional(),
// Body is expected as a string
responseType: ResponseTypeSchema.default("text")
});
var fetchToolInputSchema = import_zod7.z.object({
items: import_zod7.z.array(FetchItemSchema).min(1, "At least one fetch item is required.")
});
// src/tools/fetchTool.ts
var FetchResultItemSchema = import_zod8.z.object({
id: import_zod8.z.string().optional(),
success: import_zod8.z.boolean(),
status: import_zod8.z.number().optional(),
statusText: import_zod8.z.string().optional(),
headers: import_zod8.z.record(import_zod8.z.string()).optional(),
body: import_zod8.z.unknown().optional(),
error: import_zod8.z.string().optional(),
suggestion: import_zod8.z.string().optional()
});
var FetchToolOutputSchema = import_zod8.z.array(FetchResultItemSchema);
async function processSingleFetch(item) {
const { id, url, method = "GET", headers = {}, body, responseType = "text" } = item;
const resultItem = { id, success: false };
try {
const requestOptions = {
method,
headers
// Cast headers
};
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
requestOptions.body = body;
if (!requestOptions.headers || !requestOptions.headers["Content-Type"] && !requestOptions.headers["content-type"]) {
if (typeof requestOptions.headers !== "object" || requestOptions.headers === null || Array.isArray(requestOptions.headers)) {
requestOptions.headers = {};
}
requestOptions.headers["Content-Type"] = "application/json";
}
}
const response = await fetch(url, requestOptions);
resultItem.status = response.status;
resultItem.statusText = response.statusText;
const responseHeaders = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
resultItem.headers = responseHeaders;
let responseBody = null;
let bodyForError = null;
try {
bodyForError = await response.clone().text();
} catch {
}
if (!response.ok) {
let errorDetail = "";
if (typeof bodyForError === "string" && bodyForError.length > 0) {
errorDetail = ` - Body: ${bodyForError.substring(0, 150)}`;
}
throw new Error(`HTTP error! Status: ${response.status}${errorDetail}`);
}
try {
if (responseType === "json") {
responseBody = await response.json();
} else if (responseType === "text") {
responseBody = bodyForError;
} else {
responseBody = null;
}
} catch (parseError) {
throw new Error(
`Failed to parse response body as ${responseType}: ${parseError instanceof Error ? parseError.message : String(parseError)}`
);
}
resultItem.body = responseBody;
resultItem.success = true;
} catch (e) {
const errorMsg = e instanceof Error ? e.message : "Unknown error";
resultItem.error = `Fetch failed for ${url}: ${errorMsg}`;
if (errorMsg.includes("Failed to parse response body")) {
resultItem.suggestion = `The server responded with status ${resultItem.status}, but the response body was not valid ${responseType}. Check the 'responseType' parameter or the server's response format.`;
} else if (errorMsg.includes("HTTP error")) {
resultItem.suggestion = "The server returned an error status code. Check the error message body (if available) and the request details (URL, method, headers, body).";
} else if (errorMsg.includes("fetch") || errorMsg.includes("Network request failed")) {
resultItem.suggestion = "Check URL, network connection, DNS resolution, and potential CORS issues if running in a browser.";
} else {
resultItem.suggestion = "Check URL, network connection, method, headers, body, and potential CORS issues.";
}
resultItem.success = false;
}
return resultItem;
}
var fetchTool = (0, import_mcp_core7.defineTool)({
name: "fetch",
// Keep tool name simple
description: "Performs one or more HTTP fetch requests sequentially.",
inputSchema: fetchToolInputSchema,
outputSchema: FetchToolOutputSchema,
// Use the array schema
execute: async (input, _options) => {
const parsed = fetchToolInputSchema.safeParse(input);
if (!parsed.success) {
const errorMessages = Object.entries(parsed.error.flatten().fieldErrors).map(([field, messages]) => `${field}: ${messages.join(", ")}`).join("; ");
throw new Error(`Input validation failed: ${errorMessages}`);
}
const { items } = parsed.data;
const results = [];
for (const item of items) {
const result = await processSingleFetch(item);
results.push(result);
}
return [(0, import_mcp_core8.jsonPart)(results, FetchToolOutputSchema)];
}
});
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
GetInterfacesToolInputSchema,
GetPublicIpToolInputSchema,
downloadTool,
downloadToolInputSchema,
fetchTool,
getInterfacesTool,
getPublicIpTool
});
//# sourceMappingURL=index.cjs.map