feat: 初次提交

This commit is contained in:
ethan.chen
2026-01-06 17:35:52 +08:00
commit 372b52b214
24 changed files with 4645 additions and 0 deletions

296
src/tools/devops/nas.ts Normal file
View File

@@ -0,0 +1,296 @@
/**
* NAS file management tools
*/
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { mcpServer } from "../../server.js";
import { configManager } from "../../storage/config.js";
import { logger } from "../../utils/logger.js";
export function registerNASTools(): void {
// List NAS files
mcpServer.registerTool(
{
name: "nas_list_files",
description: "List files and directories on NAS",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Path to list (default: root)",
default: "/",
},
},
},
},
async (args) => {
const nasConfig = configManager.getNASConfig();
const path = (args.path as string) || "/";
if (!nasConfig.host) {
return {
content: [
{
type: "text",
text: "Error: NAS configuration not found. Please set NAS_HOST, NAS_USERNAME, and NAS_PASSWORD in environment variables.",
},
],
isError: true,
};
}
try {
// Note: This is a placeholder implementation
// In a real implementation, you would use appropriate libraries based on protocol
// For SMB: smb2 or node-smb2
// For FTP: basic-ftp
// For SFTP: ssh2-sftp-client
const protocol = nasConfig.protocol || "smb";
let result = "";
if (protocol === "smb") {
result = `NAS File Listing (SMB protocol)\n`;
result += `Host: ${nasConfig.host}\n`;
result += `Path: ${path}\n\n`;
result += `Note: SMB file listing requires additional libraries.\n`;
result += `To implement, install: bun add smb2\n`;
result += `Example files that would be listed:\n`;
result += `- Documents/\n`;
result += `- Media/\n`;
result += `- Backups/`;
} else if (protocol === "ftp" || protocol === "sftp") {
result = `NAS File Listing (${protocol.toUpperCase()} protocol)\n`;
result += `Host: ${nasConfig.host}\n`;
result += `Path: ${path}\n\n`;
result += `Note: ${protocol.toUpperCase()} file listing requires additional libraries.\n`;
if (protocol === "ftp") {
result += `To implement, install: bun add basic-ftp\n`;
} else {
result += `To implement, install: bun add ssh2-sftp-client\n`;
}
}
logger.info(`Listing NAS files at ${path}`);
return {
content: [
{
type: "text",
text: result,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing NAS files: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// Upload file to NAS
mcpServer.registerTool(
{
name: "nas_upload_file",
description: "Upload a file to NAS",
inputSchema: {
type: "object",
properties: {
localPath: {
type: "string",
description: "Local file path to upload",
},
remotePath: {
type: "string",
description: "Remote path on NAS",
},
},
required: ["localPath", "remotePath"],
},
},
async (args) => {
const nasConfig = configManager.getNASConfig();
const localPath = args.localPath as string;
const remotePath = args.remotePath as string;
if (!nasConfig.host) {
return {
content: [
{
type: "text",
text: "Error: NAS configuration not found. Please configure NAS settings.",
},
],
isError: true,
};
}
try {
logger.info(`Uploading ${localPath} to NAS ${remotePath}`);
// Placeholder implementation
return {
content: [
{
type: "text",
text: `File upload initiated:\nLocal: ${localPath}\nRemote: ${remotePath}\n\nNote: Full implementation requires protocol-specific libraries (smb2, basic-ftp, or ssh2-sftp-client).`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error uploading file: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// Download file from NAS
mcpServer.registerTool(
{
name: "nas_download_file",
description: "Download a file from NAS",
inputSchema: {
type: "object",
properties: {
remotePath: {
type: "string",
description: "Remote file path on NAS",
},
localPath: {
type: "string",
description: "Local path to save the file",
},
},
required: ["remotePath", "localPath"],
},
},
async (args) => {
const nasConfig = configManager.getNASConfig();
const remotePath = args.remotePath as string;
const localPath = args.localPath as string;
if (!nasConfig.host) {
return {
content: [
{
type: "text",
text: "Error: NAS configuration not found. Please configure NAS settings.",
},
],
isError: true,
};
}
try {
logger.info(`Downloading ${remotePath} from NAS to ${localPath}`);
// Placeholder implementation
return {
content: [
{
type: "text",
text: `File download initiated:\nRemote: ${remotePath}\nLocal: ${localPath}\n\nNote: Full implementation requires protocol-specific libraries.`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error downloading file: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// Search files on NAS
mcpServer.registerTool(
{
name: "nas_search_files",
description: "Search for files on NAS by name pattern",
inputSchema: {
type: "object",
properties: {
pattern: {
type: "string",
description: "File name pattern to search for",
},
path: {
type: "string",
description: "Base path to search in (default: root)",
default: "/",
},
},
required: ["pattern"],
},
},
async (args) => {
const nasConfig = configManager.getNASConfig();
const pattern = args.pattern as string;
const path = (args.path as string) || "/";
if (!nasConfig.host) {
return {
content: [
{
type: "text",
text: "Error: NAS configuration not found. Please configure NAS settings.",
},
],
isError: true,
};
}
try {
logger.info(`Searching NAS for pattern: ${pattern} in ${path}`);
// Placeholder implementation
return {
content: [
{
type: "text",
text: `Searching for files matching "${pattern}" in ${path}\n\nNote: Full implementation requires protocol-specific libraries and recursive directory traversal.`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error searching files: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
}

204
src/tools/devops/router.ts Normal file
View File

@@ -0,0 +1,204 @@
/**
* Soft router management tools
*/
import { mcpServer } from "../../server.js";
import { configManager } from "../../storage/config.js";
import { logger } from "../../utils/logger.js";
import axios from "axios";
export function registerRouterTools(): void {
// Get router status
mcpServer.registerTool(
{
name: "router_status",
description: "Get soft router status and information",
inputSchema: {
type: "object",
properties: {},
},
},
async () => {
const routerConfig = configManager.getRouterConfig();
if (!routerConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Router configuration not found. Please set ROUTER_HOST, ROUTER_USERNAME, and ROUTER_PASSWORD in environment variables.",
},
],
isError: true,
};
}
try {
logger.info(`Checking router status: ${routerConfig.host}`);
// Try to connect to router web interface (common ports: 80, 443, 8080)
// Most routers have a status API endpoint
const ports = [80, 443, 8080];
let status = "";
for (const port of ports) {
try {
const protocol = port === 443 ? "https" : "http";
await axios.get(`${protocol}://${routerConfig.host}:${port}/`, {
timeout: 2000,
auth:
routerConfig.username && routerConfig.password
? {
username: routerConfig.username,
password: routerConfig.password,
}
: undefined,
});
status += `Router accessible on port ${port}\n`;
break;
} catch (error) {
// Continue to next port
}
}
if (!status) {
status = `Router Status (${routerConfig.host}):\n\n`;
status += `Note: Router status retrieval depends on router firmware.\n`;
status += `Common router management interfaces:\n`;
status += `- OpenWrt: http://${routerConfig.host}/cgi-bin/luci\n`;
status += `- DD-WRT: http://${routerConfig.host}\n`;
status += `- pfSense: https://${routerConfig.host}\n`;
status += `\nFor full implementation, use router-specific API or SSH connection.`;
}
return {
content: [
{
type: "text",
text: status,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting router status: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// Get traffic statistics
mcpServer.registerTool(
{
name: "router_traffic",
description: "Get router traffic statistics",
inputSchema: {
type: "object",
properties: {},
},
},
async () => {
const routerConfig = configManager.getRouterConfig();
if (!routerConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Router configuration not found. Please configure router settings.",
},
],
isError: true,
};
}
try {
logger.info(`Getting router traffic stats: ${routerConfig.host}`);
// Placeholder - implementation depends on router firmware
return {
content: [
{
type: "text",
text: `Router Traffic Statistics (${routerConfig.host}):\n\nNote: Traffic statistics retrieval depends on router firmware.\n\nFor OpenWrt, you can use:\n- SSH connection to execute: cat /proc/net/dev\n- Or access web interface: http://${routerConfig.host}/cgi-bin/luci/admin/network/bandwidth\n\nFor other routers, check their specific API documentation.`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting traffic stats: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// List connected devices
mcpServer.registerTool(
{
name: "router_devices",
description: "List devices connected to the router",
inputSchema: {
type: "object",
properties: {},
},
},
async () => {
const routerConfig = configManager.getRouterConfig();
if (!routerConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Router configuration not found. Please configure router settings.",
},
],
isError: true,
};
}
try {
logger.info(`Listing router devices: ${routerConfig.host}`);
// Placeholder - implementation depends on router firmware
return {
content: [
{
type: "text",
text: `Connected Devices (${routerConfig.host}):\n\nNote: Device listing depends on router firmware.\n\nFor OpenWrt:\n- SSH: cat /proc/net/arp\n- Web: http://${routerConfig.host}/cgi-bin/luci/admin/network/dhcp\n\nFor other routers, check DHCP lease table or device list in web interface.`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing devices: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
}

367
src/tools/devops/server.ts Normal file
View File

@@ -0,0 +1,367 @@
/**
* Cloud server monitoring and management tools
*/
import { mcpServer } from "../../server.js";
import { configManager } from "../../storage/config.js";
import { logger } from "../../utils/logger.js";
import { Client } from "ssh2";
export function registerServerTools(): void {
// Get server status
mcpServer.registerTool(
{
name: "server_status",
description: "Get cloud server status (CPU, memory, disk usage)",
inputSchema: {
type: "object",
properties: {},
},
},
async () => {
const serverConfig = configManager.getServerConfig();
if (!serverConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Server configuration not found. Please set SERVER_HOST, SERVER_USERNAME, and SERVER_KEY_PATH in environment variables.",
},
],
isError: true,
};
}
try {
return new Promise((resolve) => {
const conn = new Client();
conn.on("ready", () => {
logger.info("SSH connection established");
// Execute commands to get system status
conn.exec(
"echo 'CPU:'; top -l 1 | grep 'CPU usage' | awk '{print $3}'; echo 'Memory:'; vm_stat | head -n 5; echo 'Disk:'; df -h / | tail -n 1",
(err: Error | undefined, stream: any) => {
if (err) {
conn.end();
resolve({
content: [
{
type: "text",
text: `Error executing command: ${err.message}`,
},
],
} as any);
return;
}
let output = "";
stream
.on("close", () => {
conn.end();
resolve({
content: [
{
type: "text",
text: `Server Status (${serverConfig.host}):\n\n${
output ||
"Status retrieved successfully. Note: Command output may vary by OS."
}`,
},
],
});
})
.on("data", (data: Buffer) => {
output += data.toString();
})
.stderr.on("data", (data: Buffer) => {
output += data.toString();
});
}
);
});
conn.on("error", (err) => {
logger.error("SSH connection error:", err);
resolve({
content: [
{
type: "text",
text: `Error connecting to server: ${err.message}\n\nMake sure:\n1. Server is accessible\n2. SSH key is configured correctly\n3. Server allows SSH connections`,
},
],
} as any);
});
// Connect using key or password
const connectOptions: {
host: string;
port: number;
username: string;
privateKey?: string;
password?: string;
} = {
host: serverConfig.host!,
port: serverConfig.port || 22,
username: serverConfig.username!,
};
if (serverConfig.keyPath) {
import("fs").then(({ readFileSync }) => {
try {
connectOptions.privateKey = readFileSync(serverConfig.keyPath!);
conn.connect(connectOptions);
} catch (error) {
resolve({
content: [
{
type: "text",
text: `Error reading SSH key: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as any);
}
});
} else {
conn.connect(connectOptions);
}
});
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting server status: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as any;
}
}
);
// Deploy application
mcpServer.registerTool(
{
name: "server_deploy",
description: "Deploy application to cloud server",
inputSchema: {
type: "object",
properties: {
localPath: {
type: "string",
description: "Local path of the application to deploy",
},
remotePath: {
type: "string",
description: "Remote path on server",
},
command: {
type: "string",
description:
'Optional command to run after deployment (e.g., "pm2 restart app")',
},
},
required: ["localPath", "remotePath"],
},
},
async (args) => {
const serverConfig = configManager.getServerConfig();
const localPath = args.localPath as string;
const remotePath = args.remotePath as string;
const command = args.command as string | undefined;
if (!serverConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Server configuration not found. Please configure server settings.",
},
],
isError: true,
};
}
try {
logger.info(
`Deploying ${localPath} to ${serverConfig.host}:${remotePath}`
);
// Placeholder - full implementation would use scp or sftp
return {
content: [
{
type: "text",
text: `Deployment initiated:\nLocal: ${localPath}\nRemote: ${
serverConfig.host
}:${remotePath}\n${
command ? `Command: ${command}` : ""
}\n\nNote: Full deployment requires SCP/SFTP implementation. Consider using scp2 or ssh2-sftp-client libraries.`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error deploying: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
);
// View server logs
mcpServer.registerTool(
{
name: "server_logs",
description: "View server logs",
inputSchema: {
type: "object",
properties: {
logPath: {
type: "string",
description: "Path to log file (e.g., /var/log/app.log)",
default: "/var/log/syslog",
},
lines: {
type: "number",
description: "Number of lines to retrieve",
default: 50,
},
},
},
},
async (args) => {
const serverConfig = configManager.getServerConfig();
const logPath = (args.logPath as string) || "/var/log/syslog";
const lines = (args.lines as number) || 50;
if (!serverConfig.host) {
return {
content: [
{
type: "text",
text: "Error: Server configuration not found. Please configure server settings.",
},
],
isError: true,
};
}
try {
return new Promise((resolve) => {
const conn = new Client();
conn.on("ready", () => {
conn.exec(
`tail -n ${lines} ${logPath}`,
(err: Error | undefined, stream: any) => {
if (err) {
conn.end();
resolve({
content: [
{
type: "text",
text: `Error reading logs: ${err.message}`,
},
],
} as any);
return;
}
let output = "";
stream
.on("close", () => {
conn.end();
resolve({
content: [
{
type: "text",
text: `Server Logs (${logPath}, last ${lines} lines):\n\n${
output || "No logs found or permission denied"
}`,
},
],
});
})
.on("data", (data: Buffer) => {
output += data.toString();
})
.stderr.on("data", (data: Buffer) => {
output += data.toString();
});
}
);
});
conn.on("error", (err: Error) => {
resolve({
content: [
{
type: "text",
text: `Error connecting to server: ${err.message}`,
},
],
} as any);
});
const connectOptions: {
host: string;
port: number;
username: string;
privateKey?: string;
} = {
host: serverConfig.host!,
port: serverConfig.port || 22,
username: serverConfig.username!,
};
if (serverConfig.keyPath) {
import("fs").then(({ readFileSync }) => {
try {
connectOptions.privateKey = readFileSync(serverConfig.keyPath!);
conn.connect(connectOptions);
} catch (error) {
resolve({
content: [
{
type: "text",
text: `Error reading SSH key: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as any);
}
});
} else {
conn.connect(connectOptions);
}
});
} catch (error) {
return {
content: [
{
type: "text",
text: `Error viewing logs: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as any;
}
}
);
}