feat: P0 — MCP Tool类型(36) + Server框架 + 7个核心Tool实现
S-001: packages/shared/src/mcp/tools.ts — 36 Tool接口定义(10模块) S-002: packages/shared/src/mcp/config.ts — DeviceConfig/Gateway类型 C-001: packages/server/src/index.ts — MCP Server框架+SSE传输 C-002~011: packages/server/src/tools.ts — 7个P0核心Tool - file_list, file_read, file_write, file_move - exec, process_list, sysinfo packages/server/src/main.ts — 入口, 环境变量配置
This commit is contained in:
148
packages/server/src/tools.ts
Normal file
148
packages/server/src/tools.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* P0 Core Tool Implementations
|
||||
*
|
||||
* file_list, file_read, file_write, file_move, exec, process_list, sysinfo.
|
||||
* These map to C-002 through C-011 in the backlog.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { execSync, spawn } from "node:child_process";
|
||||
import * as os from "node:os";
|
||||
|
||||
// ── C-002: file_list ────────────────────────────────────────
|
||||
export async function file_list(args: {
|
||||
path: string;
|
||||
recursive?: boolean;
|
||||
}): Promise<any> {
|
||||
const dir = args.path || ".";
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
const result = entries.map(e => ({
|
||||
name: e.name,
|
||||
type: e.isDirectory() ? "dir" : e.isFile() ? "file" : "other",
|
||||
size: e.isFile() ? fs.statSync(path.join(dir, e.name)).size : undefined,
|
||||
}));
|
||||
return { path: dir, entries: result, count: result.length };
|
||||
}
|
||||
|
||||
// ── C-003: file_read ────────────────────────────────────────
|
||||
export async function file_read(args: {
|
||||
path: string;
|
||||
encoding?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}): Promise<any> {
|
||||
const encoding = (args.encoding === "base64" ? "base64" : "utf8") as BufferEncoding;
|
||||
let content = fs.readFileSync(args.path, encoding);
|
||||
if (encoding === "utf8") {
|
||||
const lines = (content as string).split("\n");
|
||||
const start = args.offset ?? 0;
|
||||
const end = args.limit ? start + args.limit : lines.length;
|
||||
content = lines.slice(start, end).join("\n");
|
||||
}
|
||||
return { path: args.path, content, size: fs.statSync(args.path).size };
|
||||
}
|
||||
|
||||
// ── C-004: file_write ───────────────────────────────────────
|
||||
export async function file_write(args: {
|
||||
path: string;
|
||||
content: string;
|
||||
encoding?: string;
|
||||
}): Promise<any> {
|
||||
const encoding = args.encoding === "base64" ? "base64" : "utf8";
|
||||
fs.mkdirSync(path.dirname(args.path), { recursive: true });
|
||||
fs.writeFileSync(args.path, args.content, encoding as BufferEncoding);
|
||||
return { ok: true, path: args.path, size: fs.statSync(args.path).size };
|
||||
}
|
||||
|
||||
// ── C-005: file_move ────────────────────────────────────────
|
||||
export async function file_move(args: {
|
||||
from: string;
|
||||
to: string;
|
||||
}): Promise<any> {
|
||||
fs.mkdirSync(path.dirname(args.to), { recursive: true });
|
||||
fs.renameSync(args.from, args.to);
|
||||
return { ok: true, from: args.from, to: args.to };
|
||||
}
|
||||
|
||||
// ── C-008: exec ─────────────────────────────────────────────
|
||||
export async function exec(args: {
|
||||
command: string;
|
||||
cwd?: string;
|
||||
timeout?: number;
|
||||
}): Promise<any> {
|
||||
const timeout = args.timeout ?? 30000;
|
||||
try {
|
||||
const stdout = execSync(args.command, {
|
||||
cwd: args.cwd ?? process.cwd(),
|
||||
timeout,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 10 * 1024 * 1024, // 10MB
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return { ok: true, stdout: stdout.toString(), stderr: "" };
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
stdout: err.stdout?.toString() ?? "",
|
||||
stderr: err.stderr?.toString() ?? "",
|
||||
exitCode: err.status ?? -1,
|
||||
signal: err.signal,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ── C-010: process_list ─────────────────────────────────────
|
||||
export async function process_list(args: {
|
||||
filter?: string;
|
||||
}): Promise<any> {
|
||||
let stdout: string;
|
||||
try {
|
||||
if (process.platform === "win32") {
|
||||
stdout = execSync("tasklist /FO CSV /NH", { encoding: "utf8" }).toString();
|
||||
} else {
|
||||
stdout = execSync("ps aux --no-headers", { encoding: "utf8" }).toString();
|
||||
}
|
||||
} catch {
|
||||
return { error: "Failed to list processes" };
|
||||
}
|
||||
|
||||
const processes = stdout.trim().split("\n").map(line => {
|
||||
if (process.platform === "win32") {
|
||||
const [name, pid, , mem] = line.replace(/"/g, "").split(",");
|
||||
return { name: name?.trim(), pid: Number(pid), mem: mem?.trim() };
|
||||
}
|
||||
const parts = line.trim().split(/\s+/);
|
||||
return { user: parts[0], pid: Number(parts[1]), cpu: parts[2], mem: parts[3], name: parts[10] };
|
||||
});
|
||||
|
||||
const filtered = args.filter
|
||||
? processes.filter(p => p.name?.toLowerCase().includes(args.filter!.toLowerCase()))
|
||||
: processes.slice(0, 50);
|
||||
|
||||
return { count: filtered.length, processes: filtered };
|
||||
}
|
||||
|
||||
// ── C-011: sysinfo ──────────────────────────────────────────
|
||||
export async function sysinfo(_args: {}): Promise<any> {
|
||||
const cpuUsage = os.loadavg();
|
||||
const totalMem = os.totalmem();
|
||||
const freeMem = os.freemem();
|
||||
const uptime = os.uptime();
|
||||
|
||||
return {
|
||||
hostname: os.hostname(),
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
cpus: os.cpus().length,
|
||||
loadavg: cpuUsage,
|
||||
memory: {
|
||||
total: totalMem,
|
||||
free: freeMem,
|
||||
used: totalMem - freeMem,
|
||||
percent: Math.round(((totalMem - freeMem) / totalMem) * 100),
|
||||
},
|
||||
uptime,
|
||||
node: process.version,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user